# Settlement Specification

Market settlement and P&L calculation following Betfair's model.

## Overview

Settlement occurs when a market result is confirmed. The book records official runner results and transitions market state to `Settled`.

## Runner Status

| Status | Description |
|--------|-------------|
| `Winner` | Runner won (or placed, for place markets) |
| `Loser` | Runner lost |
| `Removed` | Non-runner, bets voided at this runner |
| `RemovedVacant` | Withdrawn, reduction factor may apply |

## Settlement Flow

```
1. Market closes (no more betting)
2. Result confirmed → SettleMarket command
3. Each runner assigned `RunnerResult` status
4. `BookEvent::MarketSettled` emitted
5. Market state → Settled
```

## Commands

### SettleMarket

```rust
pub enum RunnerResult {
    Winner,
    Loser,
    Removed,       // Void all bets on this runner
    RemovedVacant, // Void + potential reduction factor
}

pub struct SettleMarket {
    pub market_id: MarketId,
    pub runner_results: Vec<(RunnerId, RunnerResult)>,
    pub dead_heat_divisor: Option<u32>,  // For dead heats: divide winnings
}
```

### VoidMarket

Void entire market - all bets cancelled, stakes returned.

```rust
pub struct VoidMarket {
    pub market_id: MarketId,
    pub reason: String,
}
```

## P&L Calculation

### Back Bet Settlement

```
If runner = Winner:
    P&L = stake × (odds - 1)
If runner = Loser:
    P&L = -stake
If runner = Removed:
    P&L = 0 (stake returned)
```

### Lay Bet Settlement

```
If runner = Winner:
    P&L = -stake × (odds - 1)   // Liability lost
If runner = Loser:
    P&L = +stake                 // Keep backer's stake
If runner = Removed:
    P&L = 0 (liability returned)
```

### Dead Heat

When multiple runners are winners (dead heat):

```
effective_odds = 1 + (odds - 1) / dead_heat_divisor

Back P&L = stake × (effective_odds - 1)
Lay P&L = -stake × (effective_odds - 1)
```

Example: 2-way dead heat at odds 3.0
```
divisor = 2
effective_odds = 1 + (3.0 - 1) / 2 = 2.0
Back P&L = stake × 1.0 (instead of stake × 2.0)
```

## Events

### MarketSettled

```rust
BookEvent::MarketSettled {
    runner_results: Vec<(RunnerId, RunnerResult)>,
    dead_heat_divisor: Option<u32>,
}
```

## State After Settlement

```rust
pub enum BookMarketState {
    // ... existing states ...
    Settled,  // Terminal: result applied, P&L calculated
}
```

- `Settled` is terminal (like `Closed` or `Voided`)
- All orders are cancelled/lapsed before settlement
- No further commands accepted except queries
- The book does not emit per-trade `BetSettled` events; settlement accounting can be derived downstream from trades + `MarketSettled`

## Reduction Factors

When a runner is removed (withdrawn) before the event:

1. Betfair calculates a reduction factor based on the runner's odds
2. Winning bets on other runners have reduced payouts
3. This is handled outside the book (pricing/adjustment layer)

For simplicity, the book layer:
- Voids all bets on the removed runner
- Emits `RunnerRemoved` event with the factor
- Leaves payout adjustment to the settlement/wallet layer

```rust
BookEvent::RunnerRemoved {
    runner_id: RunnerId,
    reduction_factor_bps: Option<u32>,  // Basis points (e.g., 500 = 5%)
}
```

## Validation

- `MARKET_NOT_CLOSED`: Can only settle a closed market
- `RUNNER_NOT_FOUND`: Invalid runner in results
- `DUPLICATE_RUNNER`: Same runner listed twice
- `INCOMPLETE_RESULTS`: Not all runners have results
- Terminal markets reject further settlement commands via market-state validation

## Example

```rust
// Horse race with 3 runners, Runner 2 wins
let cmd = Command {
    correlation_id: Some(CorrelationId("settle-1".to_string())),
    metadata: None,
    market_id,
    kind: CommandKind::SettleMarket {
        runner_results: vec![
            (RunnerId(1), RunnerResult::Loser),
            (RunnerId(2), RunnerResult::Winner),
            (RunnerId(3), RunnerResult::Loser),
        ],
        dead_heat_divisor: None,
    },
};

let (events, _) = book.handle(&cmd)?;
book.apply_all_events(&events);

// Events emitted:
// - MarketSettled { ... }
// - MarketStateChanged { to: Settled, reason: "SETTLED" } (book-dependent)
```

## Sources

- [Betfair Runner Status](https://docs.developer.betfair.com/display/1smk3cen4v3lu3yomq5qye0ni/Betting+Enums)
- [Betfair Settlement FAQ](https://support.developer.betfair.com/hc/en-us/articles/115003887731-How-can-I-view-or-display-results-after-the-event)
