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

Spread Convergence

Track the price spread between two correlated markets. Enter when the spread deviates beyond a z-score threshold, exit when it reverts to the mean.

How It Works

  1. Compute spread = price_a - price_b each cycle
  2. Maintain a rolling window of spread values
  3. Compute z-score of current spread vs historical mean/std
  4. Enter when |z| > entry_zscore, exit when |z| < exit_zscore
Spread history: mean=0.02, std=0.01
Current spread: 0.05 → z-score = 3.0
Signal: long_b_short_a (spread too high, expect reversion)

Pipeline: spread_convergence

scanner = hz.spread_convergence(
    market_a="btc-100k",
    market_b="eth-5k",
    feed_a="btc_feed",
    feed_b="eth_feed",
    lookback=100,
    entry_zscore=2.0,
    exit_zscore=0.5,
    max_spread=0.20,
    size=10.0,
    auto_execute=False,
    cooldown=30.0,
)

hz.run(pipeline=[scanner], ...)
ParameterTypeDefaultDescription
market_astrrequiredFirst market ID
market_bstrrequiredSecond market ID
feed_astrrequiredFeed for market A
feed_bstrrequiredFeed for market B
lookbackint100Rolling window size
entry_zscorefloat2.0Z-score to enter
exit_zscorefloat0.5Z-score to exit
max_spreadfloat0.20Max absolute spread to consider valid
sizefloat10.0Trade size per leg
auto_executeboolFalseAuto-execute signals
cooldownfloat30.0Seconds between trades

SpreadSignal

Stored in ctx.params["last_spread_signal"].
FieldTypeDescription
market_astrFirst market
market_bstrSecond market
spreadfloatCurrent spread value
spread_meanfloatRolling mean
spread_stdfloatRolling standard deviation
zscorefloatCurrent z-score
signalstr"long_a_short_b", "long_b_short_a", "exit", or "hold"

Signal Logic

ConditionPositionSignal
z > entry_zscoreflatlong_b_short_a
z < -entry_zscoreflatlong_a_short_b
`z< exit_zscore`positionedexit
otherwiseanyhold

Example

import horizon as hz

# Spread trade between two correlated political markets
spread = hz.spread_convergence(
    market_a="gop-senate",
    market_b="gop-house",
    feed_a="gop_senate_feed",
    feed_b="gop_house_feed",
    lookback=200,
    entry_zscore=2.5,
    exit_zscore=0.3,
    auto_execute=True,
)

hz.run(
    name="spread_trader",
    exchanges=[hz.Polymarket(private_key="0x...")],
    markets=["gop-senate", "gop-house"],
    feeds={
        "gop_senate_feed": hz.PolymarketBook("gop-senate"),
        "gop_house_feed": hz.PolymarketBook("gop-house"),
    },
    pipeline=[spread],
    interval=1.0,
)
For more rigorous pairs trading with cointegration testing and half-life filtering, see Statistical Arb.