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.
When a prediction market event has multiple outcomes (e.g., 4 candidates), their prices should sum to $1.00. Deviations create arbitrage opportunities.
Full Code
"""Multi-outcome arb: 4-candidate election event."""
import horizon as hz
# Register a multi-outcome event
event = hz.Event(
event_id="presidential-election-2028",
outcomes=[
hz.Outcome(market_id="candidate-a", name="Candidate A"),
hz.Outcome(market_id="candidate-b", name="Candidate B"),
hz.Outcome(market_id="candidate-c", name="Candidate C"),
hz.Outcome(market_id="candidate-d", name="Candidate D"),
],
)
# One-shot scan for mispricing
engine = hz.Engine()
engine.register_event(event)
# Start feeds for all outcomes
for outcome in event.outcomes:
engine.start_feed(
outcome.market_id,
"polymarket_book",
config_json=f'{{"condition_id": "{outcome.market_id}"}}',
)
# Scan for arb
result = hz.event_arb_scanner(
engine,
event_id="presidential-election-2028",
min_edge=0.005,
)
if result:
print(f"Event arb found!")
print(f" Sum of asks: ${result.total_ask:.4f}")
print(f" Edge: ${result.edge:.4f} per full set")
print(f" Recommended sizes: {result.sizes}")
else:
print("No multi-outcome arb found")
How It Works
Event groups related outcomes that must sum to $1.00
event_arb_scanner() sums the best ask across all outcomes
- If
total_ask < 1.00, buying all outcomes locks in a risk-free profit of 1.00 - total_ask
- Sizing is proportional: cheaper outcomes get more contracts to balance dollar exposure
Live Pipeline
Run the scanner continuously inside hz.run():
"""Continuous multi-outcome arb scanner."""
import horizon as hz
from horizon.context import FeedData
def fair_value(ctx: hz.Context) -> float:
"""Use Candidate A feed as primary."""
feed = ctx.feeds.get("candidate-a", FeedData())
return feed.price if feed.price > 0 else 0.25
event = hz.Event(
event_id="presidential-election-2028",
outcomes=[
hz.Outcome(market_id="candidate-a", name="Candidate A"),
hz.Outcome(market_id="candidate-b", name="Candidate B"),
hz.Outcome(market_id="candidate-c", name="Candidate C"),
hz.Outcome(market_id="candidate-d", name="Candidate D"),
],
)
scanner = hz.event_arb_sweep
hz.run(
name="multi_outcome_arb",
events=[event],
markets=["candidate-a", "candidate-b", "candidate-c", "candidate-d"],
feeds={
"candidate-a": hz.PolymarketBook("candidate-a"),
"candidate-b": hz.PolymarketBook("candidate-b"),
"candidate-c": hz.PolymarketBook("candidate-c"),
"candidate-d": hz.PolymarketBook("candidate-d"),
},
pipeline=[fair_value],
risk=hz.Risk(max_position=100, max_drawdown_pct=3),
interval=1.0,
mode="paper",
)
Run It
python examples/multi_outcome_arb.py
Composite Scanner
Combine parity, cross-exchange, and multi-outcome scanning for maximum coverage:
scanner = hz.composite_arb(
methods=[
hz.ArbMethodConfig(method="parity", market_id="candidate-a", feed_name="candidate-a"),
hz.ArbMethodConfig(
method="event",
event_id="presidential-election-2028",
),
hz.ArbMethodConfig(
method="cross_exchange",
market_id="candidate-a",
exchanges=["polymarket", "kalshi"],
feed_map={"polymarket": "candidate-a", "kalshi": "kalshi-candidate-a"},
),
],
min_edge=0.005,
auto_execute=True,
)
See Multi-Outcome Arbitrage for the full method reference.