Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com
Trade the spread between two cointegrated prediction markets. When the spread deviates from its mean, enter positions expecting reversion.

Full Code

"""Stat arb pairs trading: GOP Senate vs House markets."""

import horizon as hz
from horizon.context import FeedData


def fair_value(ctx: hz.Context) -> float:
    """Use the Senate feed as primary fair value."""
    senate = ctx.feeds.get("gop_senate_feed", FeedData())
    return senate.price if senate.price > 0 else 0.50


# Check cointegration first
coint = hz.cointegration_test(
    series_a=[0.52, 0.53, 0.51, 0.54, 0.55, 0.53, 0.52, 0.54, 0.56, 0.55],
    series_b=[0.48, 0.49, 0.47, 0.50, 0.51, 0.49, 0.48, 0.50, 0.52, 0.51],
)
print(f"Cointegrated: {coint.is_cointegrated}")
print(f"ADF stat: {coint.adf_statistic:.4f}, p-value: {coint.p_value:.4f}")
print(f"Hedge ratio: {coint.hedge_ratio:.4f}")

# Configure stat arb
config = hz.StatArbConfig(
    market_a="gop-senate",
    market_b="gop-house",
    feed_a="gop_senate_feed",
    feed_b="gop_house_feed",
    hedge_ratio=coint.hedge_ratio,
    entry_z=2.0,        # enter at 2 standard deviations
    exit_z=0.5,         # exit at 0.5 standard deviations
    lookback=50,         # rolling window for z-score
    max_size=20.0,
)

stat_arb_pipeline = hz.stat_arb(config, auto_execute=True)

hz.run(
    name="stat_arb_pairs",
    markets=["gop-senate", "gop-house"],
    feeds={
        "gop_senate_feed": hz.PolymarketBook("gop-senate"),
        "gop_house_feed": hz.PolymarketBook("gop-house"),
    },
    pipeline=[fair_value, stat_arb_pipeline],
    risk=hz.Risk(max_position=100, max_drawdown_pct=5),
    interval=1.0,
    mode="paper",
)

How It Works

  1. cointegration_test() verifies the two price series share a long-run equilibrium
  2. StatArbConfig sets entry/exit z-score thresholds and the hedge ratio
  3. stat_arb() monitors the spread z-score each cycle:
  • When z > entry_z: short the spread (sell A, buy B)
  • When z < -entry_z: long the spread (buy A, sell B)
  • When |z| < exit_z: close the position
  1. The hedge ratio scales the B-side size to maintain dollar-neutrality

Spread Z-Score

You can also compute the z-score manually for custom logic:
z = hz.spread_zscore(
    price_a=0.55,
    price_b=0.51,
    hedge_ratio=0.95,
    mean=0.02,
    std=0.015,
)
print(f"Spread z-score: {z:.2f}")

Run It

python examples/stat_arb_pairs.py

Simpler Alternative

For a lighter approach without cointegration testing, use spread_convergence():
spread_pipe = hz.spread_convergence(
    market_a="gop-senate",
    market_b="gop-house",
    feed_a="gop_senate_feed",
    feed_b="gop_house_feed",
    threshold=0.03,      # trade when spread exceeds 3 cents
    target_spread=0.01,  # expect convergence to 1 cent
    size=10.0,
)
See Statistical Arbitrage for the full method reference.