Multi-outcome events are prediction markets with more than two possible outcomes - for example, “Who wins the 2024 election?” with candidates Trump, Biden, DeSantis, etc. Each outcome is a separate binary contract with its own YES/NO tokens.
Horizon models these as an Event containing multiple Outcome objects, layered on top of the existing binary Market model. Zero breaking changes to existing strategies.
Core Concepts
Each Outcome is a Binary Contract
Every outcome in a multi-outcome event is itself a standard binary contract. You can:
- Buy YES on an outcome (bet it will happen)
- Sell YES on an outcome (exit a YES position)
- Buy NO on an outcome (bet it won’t happen)
- Sell NO on an outcome (exit a NO position)
The full Side × OrderSide matrix works exactly like single-market trading.
Event Parity
For a well-formed event, the sum of all outcome YES prices should be approximately 1.0. If the sum deviates significantly, there may be an arbitrage opportunity:
- Sum under 1.0: Buy all outcomes = guaranteed profit (after fees)
- Sum over 1.0: Sell all outcomes (buy all NOs) = guaranteed profit (after fees)
Types
Outcome
A single named outcome in a multi-outcome event.
Event
Groups outcomes under a shared event/condition.
Event → Markets
Event.to_markets() converts each outcome into a standard Market with event_id and outcome_name set:
These markets work with all existing engine methods - submit_order, submit_quotes, tick, etc.
Discovery
Discover multi-outcome events from Polymarket:
discover_events queries the Polymarket Gamma API and groups markets by condition_id into events. Each market question is parsed to extract the outcome name.
Engine Event Management
Registering Events
Register events on the engine to enable event-level risk limits and exposure tracking:
Event Exposure & Positions
Event Parity Check
Check whether outcome prices sum to ~1.0 using feed data:
Event Risk Limits
Set max_position_per_event to cap total exposure across all outcomes in an event:
hz.Risk() does not support max_position_per_event. Use RiskConfig directly for event-level position limits.
When a market is registered in an event, orders are checked against both the per-market and per-event limits. Markets not in any event are unaffected.
Event risk limits are optional. Set max_position_per_event=None (the default) to disable event-level risk checks entirely.
Running with Events
Pass events directly to hz.run():
When events is provided:
- Each event is registered on the engine automatically
- Each outcome is converted to a
Market via to_markets()
- The pipeline runs once per outcome per cycle
ctx.event is set to the current event being processed
ctx.market.event_id and ctx.market.outcome_name are populated
events and markets are independent parameters. Events expand into markets automatically - don’t list the same market IDs in both.
Arbitrage Detection
Detect event-level arbitrage opportunities using EventArbitrageOpportunity:
Example: Multi-Outcome Market Maker