Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com

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.
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.

When to Use What

GoalFunctionPage
Mirror a known walletcopy_trades() / copy_trader()This page
Auto-fade a bad walletcopy_trades(inverse=True)This page
Score first, then auto-executehunt()Wallet Profiler
Discover + auto-copy whalesgalaxy_hunt()Whale Galaxy
Score or analyze a wallet firstscore_wallet() / analyze_wallet()Wallet Intelligence

Overview

Standalone Mode

hz.copy_trades() runs a blocking poll loop - the simplest way to start.

Pipeline Mode

hz.copy_trader() returns a pipeline function for use inside hz.run().

Inverse (Fade) Mode

Flip buy/sell to fade the whale’s trades instead of copying them.

Dedup + Watermarks

FIFO bounded dedup (10k capacity) with per-wallet timestamp watermarks prevents replaying history.

Standalone Mode: hz.copy_trades

The simplest entry point. Creates an Engine, seeds dedup state, and runs a blocking poll loop.
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"
)
ParameterTypeDefaultDescription
walletstr or list[str]requiredWallet address(es) to copy
walletslist[str]NoneAlternative to wallet for multiple addresses
size_scalefloat1.0Multiplier for trade size (0.5 = half size)
max_positionfloat1000.0Max USDC exposure per market
min_trade_usdcfloat1.0Ignore trades smaller than this
poll_intervalfloat30.0Seconds between polls
exchangestr"paper"Exchange backend ("paper" or "polymarket")
inverseboolFalseFade the whale (flip buy/sell)
max_slippagefloat0.02Max price deviation from source
dry_runboolFalseLog trades without submitting orders
riskRisk or RiskConfigNoneRisk configuration for the engine
api_keystrNoneHorizon 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.
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,
)
ParameterTypeDefaultDescription
walletstr or list[str]requiredWallet address(es) to copy
walletslist[str]NoneAlternative to wallet
size_scalefloat1.0Size multiplier
max_positionfloat1000.0Max USDC per market
min_trade_usdcfloat1.0Min trade size
inverseboolFalseFade mode
max_slippagefloat0.02Max slippage
dry_runboolFalseDry 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 SideSource Outcome→ Side→ OrderSide
BUYYesYesBuy
BUYNoNoBuy
SELLYesYesSell
SELLNoNoSell
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

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:
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)

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)

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

import horizon as hz

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

Pipeline Mode with Market Making

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

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

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,
)
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.