> ## Documentation Index
> Fetch the complete documentation index at: https://mathematicalcompany.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Copy-Trading

> Automatically copy trades from Polymarket whale wallets with size scaling, position limits, and inverse (fade) mode.

<Note>
  **Pro Feature.** Requires a Pro or Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Note>

# Copy-Trading

Horizon's copy-trading module monitors Polymarket wallets and automatically mirrors their trades through the Engine. It supports size scaling, position limits per market, deduplication, and an inverse mode for fading whale activity.

<Note>
  Copy-trading uses Polymarket's public Data API (`hz.get_wallet_trades()`) to poll for new trades, then submits mirrored orders through the Horizon Engine with full risk pipeline protection. No access to the source wallet's private key is needed.
</Note>

### When to Use What

| Goal                            | Function                              | Page                                        |
| ------------------------------- | ------------------------------------- | ------------------------------------------- |
| Mirror a known wallet           | `copy_trades()` / `copy_trader()`     | This page                                   |
| Auto-fade a bad wallet          | `copy_trades(inverse=True)`           | This page                                   |
| Score first, then auto-execute  | `hunt()`                              | [Wallet Profiler](/wallet-profiler)         |
| Discover + auto-copy whales     | `galaxy_hunt()`                       | [Whale Galaxy](/whale-galaxy)               |
| Score or analyze a wallet first | `score_wallet()` / `analyze_wallet()` | [Wallet Intelligence](/wallet-intelligence) |

## Overview

<CardGroup cols={2}>
  <Card title="Standalone Mode" icon="play">
    `hz.copy_trades()` runs a blocking poll loop - the simplest way to start.
  </Card>

  <Card title="Pipeline Mode" icon="diagram-project">
    `hz.copy_trader()` returns a pipeline function for use inside `hz.run()`.
  </Card>

  <Card title="Inverse (Fade) Mode" icon="arrows-rotate">
    Flip buy/sell to fade the whale's trades instead of copying them.
  </Card>

  <Card title="Dedup + Watermarks" icon="filter">
    FIFO bounded dedup (10k capacity) with per-wallet timestamp watermarks prevents replaying history.
  </Card>
</CardGroup>

***

## Standalone Mode: hz.copy\_trades

The simplest entry point. Creates an Engine, seeds dedup state, and runs a blocking poll loop.

```python theme={null}
import horizon as hz

hz.copy_trades(
    wallet="0x1234abcd...",
    size_scale=0.5,           # Copy at 50% of whale's size
    max_position=1000.0,      # Max $1000 exposure per market
    poll_interval=30.0,       # Check every 30 seconds
    exchange="paper",         # "paper" or "polymarket"
)
```

| Parameter        | Type                 | Default   | Description                                    |
| ---------------- | -------------------- | --------- | ---------------------------------------------- |
| `wallet`         | `str or list[str]`   | required  | Wallet address(es) to copy                     |
| `wallets`        | `list[str]`          | `None`    | Alternative to `wallet` for multiple addresses |
| `size_scale`     | `float`              | `1.0`     | Multiplier for trade size (0.5 = half size)    |
| `max_position`   | `float`              | `1000.0`  | Max USDC exposure per market                   |
| `min_trade_usdc` | `float`              | `1.0`     | Ignore trades smaller than this                |
| `poll_interval`  | `float`              | `30.0`    | Seconds between polls                          |
| `exchange`       | `str`                | `"paper"` | Exchange backend (`"paper"` or `"polymarket"`) |
| `inverse`        | `bool`               | `False`   | Fade the whale (flip buy/sell)                 |
| `max_slippage`   | `float`              | `0.02`    | Max price deviation from source                |
| `dry_run`        | `bool`               | `False`   | Log trades without submitting orders           |
| `risk`           | `Risk or RiskConfig` | `None`    | Risk configuration for the engine              |
| `api_key`        | `str`                | `None`    | Horizon API key                                |

**Behavior:**

1. Creates an Engine with the specified exchange backend
2. Seeds dedup state from the last 100 trades per wallet (prevents replaying history)
3. Polls `get_wallet_trades()` each cycle for new trades
4. Filters by min size, valid price, and timestamp watermark
5. Applies size scaling and position limit clamping
6. Submits `OrderRequest` through the Engine (with full risk pipeline)
7. Handles SIGINT/SIGTERM for graceful shutdown

***

## Pipeline Mode: hz.copy\_trader

For use inside `hz.run()` alongside other pipeline functions.

```python theme={null}
import horizon as hz

hz.run(
    name="whale_copier",
    markets=["some-market"],
    pipeline=[
        hz.copy_trader(
            wallet="0x1234abcd...",
            size_scale=0.5,
            max_position=1000.0,
        ),
    ],
    interval=30.0,
)
```

| Parameter        | Type               | Default  | Description                |
| ---------------- | ------------------ | -------- | -------------------------- |
| `wallet`         | `str or list[str]` | required | Wallet address(es) to copy |
| `wallets`        | `list[str]`        | `None`   | Alternative to `wallet`    |
| `size_scale`     | `float`            | `1.0`    | Size multiplier            |
| `max_position`   | `float`            | `1000.0` | Max USDC per market        |
| `min_trade_usdc` | `float`            | `1.0`    | Min trade size             |
| `inverse`        | `bool`             | `False`  | Fade mode                  |
| `max_slippage`   | `float`            | `0.02`   | Max slippage               |
| `dry_run`        | `bool`             | `False`  | Dry run mode               |

The pipeline function:

* Uses `ctx.params["engine"]` for direct order submission (same pattern as `arb_scanner`)
* Seeds dedup on first call only
* Stores results in `ctx.params["copy_trade_results"]`
* Returns `None` (bypasses the quote mechanism - orders are submitted directly)

***

## Side Mapping

Copy-trading maps source trades to orders as follows:

| Source Side | Source Outcome | → Side | → OrderSide |
| ----------- | -------------- | ------ | ----------- |
| BUY         | Yes            | Yes    | Buy         |
| BUY         | No             | No     | Buy         |
| SELL        | Yes            | Yes    | Sell        |
| SELL        | No             | No     | Sell        |

**Inverse mode** flips `OrderSide` only (Buy ↔ Sell). The `Side` (Yes/No) stays the same. This means inverse mode buys when the whale sells, and sells when the whale buys - fading their directional conviction.

***

## Size Calculation

Orders are sized as follows:

```
scaled_usdc = trade.usdc_size × size_scale
token_size  = scaled_usdc / trade.price
```

For buy orders, the `scaled_usdc` is clamped to the remaining position limit:

```
allowed = max(0, max_position - current_exposure)
scaled_usdc = min(scaled_usdc, allowed)
```

The submitted price includes slippage protection:

* **Buy:** `min(0.99, source_price + max_slippage)`
* **Sell:** `max(0.01, source_price - max_slippage)`

***

## Dedup Strategy

Copy-trading uses a FIFO bounded dedup set (same pattern as Polymarket/Kalshi fill dedup in the Rust core):

* **Primary key:** `tx_hash` (falls back to `wallet:condition_id:timestamp:size` composite)
* **Capacity:** 10,000 entries with FIFO eviction
* **Seeding:** On startup, the last 100 trades per wallet are marked as seen
* **Watermark:** Per-wallet timestamp - only trades newer than the last seen are processed

***

## Data Types

### CopyTraderConfig

```python theme={null}
config = hz.CopyTraderConfig(
    wallets=["0x1234..."],
    size_scale=0.5,
    max_position_per_market=1000.0,
    min_trade_usdc=1.0,
    poll_interval=30.0,
    inverse=False,
    max_slippage=0.02,
    dry_run=False,
    dedup_capacity=10_000,
)
```

### CopyTradeResult

Each processed trade produces a result record:

```python theme={null}
result.source_wallet     # "0x1234..." source wallet
result.source_tx_hash    # Transaction hash (or None)
result.market_slug       # Market slug
result.condition_id      # Condition ID
result.side              # "yes" or "no"
result.order_side        # "buy" or "sell"
result.size              # Token size submitted
result.price             # Price submitted (with slippage)
result.order_id          # Engine order ID (None if dry_run)
result.error             # Error message (None on success)
```

***

## Examples

### Copy a Single Whale (Paper Mode)

```python theme={null}
import horizon as hz

hz.copy_trades(
    wallet="0xd91ef12a78d5296661816eec300753405cc1e577",
    size_scale=0.5,
    max_position=500.0,
    poll_interval=30.0,
)
```

### Fade a Whale (Inverse Mode)

```python theme={null}
import horizon as hz

hz.copy_trades(
    wallet="0xd91ef12a78d5296661816eec300753405cc1e577",
    size_scale=0.25,
    inverse=True,       # Sell when they buy, buy when they sell
    dry_run=True,       # Test first
)
```

### Copy Multiple Wallets

```python theme={null}
import horizon as hz

hz.copy_trades(
    wallets=[
        "0xd91ef12a78d5296661816eec300753405cc1e577",
        "0xabcdef1234567890abcdef1234567890abcdef12",
    ],
    size_scale=0.25,
    max_position=500.0,
)
```

### Pipeline Mode with Market Making

```python theme={null}
import horizon as hz

hz.run(
    name="copy_and_mm",
    markets=["election-winner"],
    feeds={"book": hz.PolymarketBook("election-winner")},
    pipeline=[
        hz.copy_trader(wallet="0x1234...", size_scale=0.5),
        hz.market_maker(feed_name="book", gamma=0.5, size=5.0),
    ],
    interval=30.0,
)
```

### Live Trading with Risk Controls

```python theme={null}
import horizon as hz

hz.copy_trades(
    wallet="0x1234...",
    size_scale=0.5,
    max_position=1000.0,
    exchange="polymarket",
    risk=hz.Risk(
        max_position_size=200.0,
        max_total_exposure=2000.0,
        max_drawdown=0.10,
    ),
)
```

### Access Results in Pipeline

```python theme={null}
import horizon as hz

def log_copies(ctx):
    results = ctx.params.get("copy_trade_results", [])
    for r in results:
        if r.error:
            print(f"Failed: {r.error}")
        else:
            print(f"Copied: {r.order_side} {r.side} {r.size} @ {r.price}")
    return None

hz.run(
    name="copy_with_logging",
    markets=["election-winner"],
    pipeline=[
        hz.copy_trader(wallet="0x1234...", size_scale=0.5),
        log_copies,
    ],
    interval=30.0,
)
```

<Warning>
  Copy-trading submits real orders when `exchange="polymarket"`. Always test with `dry_run=True` or `exchange="paper"` first. Position limits and the risk pipeline provide safety guardrails, but you are responsible for monitoring your exposure. The copy-trader polls the public Data API, which may have a delay of a few seconds - you will not get the exact same fill price as the source wallet.
</Warning>
