Documentation Index
Fetch the complete documentation index at: https://mathematicalcompany.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
A cross-exchange strategy that monitors the same event on both Polymarket and Kalshi, quoting tight when prices agree and wide when they diverge. Uses the multi-exchange engine with netting pairs.
Full Code
"""Cross-exchange arbitrage between Polymarket and Kalshi."""
import horizon as hz
def fair_value(ctx: hz.Context) -> float:
"""Average of both exchange book prices."""
poly_price = ctx.feeds.get("poly", hz.context.FeedData()).price or 0.5
kalshi_price = ctx.feeds.get("kalshi", hz.context.FeedData()).price or 0.5
return (poly_price + kalshi_price) / 2
def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
"""Tight spread when exchanges agree, wider when they diverge."""
poly_price = ctx.feeds.get("poly", hz.context.FeedData()).price or fair
kalshi_price = ctx.feeds.get("kalshi", hz.context.FeedData()).price or fair
divergence = abs(poly_price - kalshi_price)
base_spread = 0.02
spread = base_spread + divergence * 0.5
return hz.quotes(fair, spread, size=5)
if __name__ == "__main__":
hz.run(
name="cross_exchange_arb",
exchanges=[
hz.Polymarket(private_key="0x_demo_key"),
hz.Kalshi(api_key="demo_key"),
],
markets=["will-btc-hit-100k-by-end-of-2025", "KXBTC-25FEB16"],
feeds={
"poly": hz.PolymarketBook("will-btc-hit-100k-by-end-of-2025"),
"kalshi": hz.KalshiBook("KXBTC-25FEB16"),
},
pipeline=[fair_value, quoter],
risk=hz.Risk(max_position=50, max_drawdown_pct=3),
interval=0.5,
mode="paper",
netting_pairs=[
("will-btc-hit-100k-by-end-of-2025", "KXBTC-25FEB16"),
],
)
Key Features
Multi-Exchange Engine
Uses exchanges=[...] instead of exchange= to register both Polymarket and Kalshi:
exchanges=[
hz.Polymarket(private_key="0x_demo_key"),
hz.Kalshi(api_key="demo_key"),
],
The first exchange (Polymarket) becomes the primary. Orders are routed based on market.exchange.
Netting Pairs
The two markets represent the same underlying event on different exchanges. Registering them as a netting pair reduces the portfolio notional for hedged positions:
netting_pairs=[
("will-btc-hit-100k-by-end-of-2025", "KXBTC-25FEB16"),
],
If you hold 50 contracts on Polymarket and 30 on Kalshi, 30 contracts are considered hedged.
Divergence-Based Spread
The spread widens proportionally to the price divergence between exchanges:
spread = 0.02 + |poly_price - kalshi_price| × 0.5
- When prices agree (divergence ≈ 0): spread = 2 cents (aggressive)
- When prices diverge by 10 cents: spread = 7 cents (conservative)
Dual Feeds
Both exchange orderbooks provide real-time price data:
feeds={
"poly": hz.PolymarketBook("will-btc-hit-100k-by-end-of-2025"),
"kalshi": hz.KalshiBook("KXBTC-25FEB16"),
},
Run It
# Paper mode
python examples/cross_exchange_arb.py
# Live mode with dashboard
python -m horizon run examples/cross_exchange_arb.py --mode=live --dashboard
Strategy Variants
Directional arb
Instead of market-making both sides, take directional positions when divergence exceeds a threshold:
def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
poly = ctx.feeds.get("poly", hz.context.FeedData()).price or fair
kalshi = ctx.feeds.get("kalshi", hz.context.FeedData()).price or fair
if poly - kalshi > 0.05:
# Poly expensive, Kalshi cheap → buy Kalshi, sell Poly
return hz.quotes(fair, spread=0.01, size=10)
elif kalshi - poly > 0.05:
# Kalshi expensive, Poly cheap → buy Poly, sell Kalshi
return hz.quotes(fair, spread=0.01, size=10)
else:
# No arb opportunity
return hz.quotes(fair, spread=0.06, size=2)
Using the Arb Scanner
For automated scanning and execution, use hz.arb_scanner():
hz.run(
name="auto_arb",
exchanges=[
hz.Polymarket(private_key="0x_demo_key"),
hz.Kalshi(api_key="demo_key"),
],
markets=["will-btc-hit-100k-by-end-of-2025"],
feeds={
"poly": hz.PolymarketBook("will-btc-hit-100k-by-end-of-2025"),
"kalshi": hz.KalshiBook("KXBTC-25FEB16"),
},
pipeline=[hz.arb_scanner(
market_id="will-btc-hit-100k-by-end-of-2025",
exchanges=["polymarket", "kalshi"],
feed_map={"polymarket": "poly", "kalshi": "kalshi"},
min_edge=0.02,
auto_execute=True,
cooldown=10.0,
)],
interval=0.5,
)
Or use hz.arb_sweep() for one-shot scanning:
result = hz.arb_sweep(engine, "will-btc-hit-100k", feed_map={"polymarket": "poly", "kalshi": "kalshi"})
if result:
print(f"Arb found: {result.net_edge:.4f} edge")
See Arbitrage Executor for the full guide.