Skip to main content
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:
  1. Each event is registered on the engine automatically
  2. Each outcome is converted to a Market via to_markets()
  3. The pipeline runs once per outcome per cycle
  4. ctx.event is set to the current event being processed
  5. 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