---
title: "Multi-Outcome Arb"
description: "Event-level arbitrage when N outcome prices don't sum to $1.00."
icon: "chart-pie"
---

<Note>
  **Pro Feature.** Requires a Pro or Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Note>

# 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

```python
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

```python
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

```python
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], ...)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `event_id` | `str` | required | Registered event ID |
| `min_edge` | `float` | `0.01` | Minimum net edge |
| `max_size` | `float` | `50.0` | Maximum total size |
| `fee_rate` | `float` | `0.002` | Fee rate per outcome |
| `auto_execute` | `bool` | `False` | Auto-execute |
| `cooldown` | `float` | `10.0` | Seconds between executions |
| `proportional` | `bool` | `True` | Inverse-proportional sizing |

Stores `EventArbitrageOpportunity` in `ctx.params["last_event_arb"]`.

## One-Shot: event_arb_sweep

```python
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

| Field | Type | Description |
|-------|------|-------------|
| `event_id` | `str` | Event identifier |
| `direction` | `str` | `"buy_all"` or `"sell_all"` |
| `price_sum` | `float` | Sum of all outcome prices |
| `net_edge` | `float` | Edge after fees |
| `size` | `float` | Trade size |
| `order_ids` | `list[str]` | Order IDs for each leg |
| `timestamp` | `float` | Execution timestamp |

<Note>
  Register events with `engine.register_event(event_id, market_ids)` before scanning. Each market needs a corresponding feed for price data.
</Note>

---

## 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.

```python
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": ...}
# ]
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `markets` | `list[str]` | Market IDs to monitor |
| `implications` | `list[tuple[str, str]]` | `(A, B)` pairs where A implies B |
| `min_edge` | `float` | Minimum 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.

```python
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": ...}
# ]
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `markets` | `list[str]` | Market IDs to monitor |
| `contradictions` | `list[tuple[str, str]]` | `(A, B)` pairs that are mutually exclusive |
| `min_edge` | `float` | Minimum sum violation above 1.0 to flag (default 0.01) |
