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

Multi-Outcome Arbitrage

When an event has N outcomes (e.g., 4 candidates in an election), the sum of all YES prices should equal $1.00. If it doesn’t, there’s an arbitrage opportunity.

How It Works

  • Sum < $1.00 - fees: Buy all outcomes. One will resolve to $1.00, so you profit the difference.
  • Sum > $1.00 + fees: Sell all outcomes (buy all NOs). You collect more than $1.00 upfront.
Trump:    $0.30
Biden:    $0.25
DeSantis: $0.20
Other:    $0.15
Sum:      $0.90 (< $1.00)
Edge:     $0.10 - fees
Action:   Buy all outcomes

Engine Methods

scan_event_arb

engine.register_event("election", ["trump", "biden", "desantis", "other"])

opp = engine.scan_event_arb("election", fee_rate=0.002)
# Returns EventArbitrageOpportunity or None

execute_event_arb

order_ids = engine.execute_event_arb(
    event_id="election",
    direction="buy_all",   # or "sell_all"
    size=50.0,
    proportional=True,     # Size inversely proportional to price
)
N-leg atomic execution. If any leg fails, all prior legs are canceled. When proportional=True: cheaper outcomes get more contracts so cost is balanced across legs.

Pipeline: event_arb_scanner

scanner = hz.event_arb_scanner(
    event_id="election",
    min_edge=0.01,
    max_size=50.0,
    fee_rate=0.002,
    auto_execute=True,
    cooldown=10.0,
    proportional=True,
)

hz.run(pipeline=[scanner], ...)
ParameterTypeDefaultDescription
event_idstrrequiredRegistered event ID
min_edgefloat0.01Minimum net edge
max_sizefloat50.0Maximum total size
fee_ratefloat0.002Fee rate per outcome
auto_executeboolFalseAuto-execute
cooldownfloat10.0Seconds between executions
proportionalboolTrueInverse-proportional sizing
Stores EventArbitrageOpportunity in ctx.params["last_event_arb"].

One-Shot: event_arb_sweep

result = hz.event_arb_sweep(engine, "election", min_edge=0.01)

if result:
    print(f"Direction: {result.direction}, Edge: {result.net_edge:.4f}")
    print(f"Order IDs: {result.order_ids}")
Returns EventArbResult or None.

EventArbResult

FieldTypeDescription
event_idstrEvent identifier
directionstr"buy_all" or "sell_all"
price_sumfloatSum of all outcome prices
net_edgefloatEdge after fees
sizefloatTrade size
order_idslist[str]Order IDs for each leg
timestampfloatExecution timestamp
Register events with engine.register_event(event_id, market_ids) before scanning. Each market needs a corresponding feed for price data.

Logical Arb Scanners

Beyond price-sum arbitrage, Horizon detects logical relationship violations between related markets.

hz.implication_arb_scanner

For markets where A implies B (e.g., “Biden wins” implies “Democrat wins”), the price of A must be less than or equal to the price of B. Violations are arbs.
scanner = hz.implication_arb_scanner(
    markets=["biden-wins", "dem-wins", "trump-wins", "gop-wins"],
    implications=[
        ("biden-wins", "dem-wins"),     # Biden wins => Democrat wins
        ("trump-wins", "gop-wins"),     # Trump wins => GOP wins
    ],
    min_edge=0.02,
)

hz.run(pipeline=[scanner], ...)
# Injects ctx.params["implication_arbs"] = [
#   {"type": "implication", "market_a": ..., "market_b": ..., "edge": ..., "action": ...}
# ]
ParameterTypeDescription
marketslist[str]Market IDs to monitor
implicationslist[tuple[str, str]](A, B) pairs where A implies B
min_edgefloatMinimum price violation to flag (default 0.01)

hz.contradiction_arb_scanner

For mutually exclusive markets (e.g., “Trump wins” and “Biden wins”), the sum of prices must be at most 1.0. If P(A) + P(B) > 1, sell both.
scanner = hz.contradiction_arb_scanner(
    markets=["trump-wins", "biden-wins"],
    contradictions=[
        ("trump-wins", "biden-wins"),  # Can't both win
    ],
    min_edge=0.02,
)

hz.run(pipeline=[scanner], ...)
# Injects ctx.params["contradiction_arbs"] = [
#   {"type": "contradiction", "market_a": ..., "price_sum": ..., "edge": ..., "action": ...}
# ]
ParameterTypeDescription
marketslist[str]Market IDs to monitor
contradictionslist[tuple[str, str]](A, B) pairs that are mutually exclusive
min_edgefloatMinimum sum violation above 1.0 to flag (default 0.01)