# Settle bets from results

Match your own bets to graded outcomes and work out what each one returns.

## What it does

You keep a list of bets: the event, the market, the side, the line, the stake and the price taken. The program reads the graded results for the event and prints, for each bet, its verdict and what it returns.

Results come from official sources. Each outcome has a `verdict`:

| Verdict | Return |
| --- | --- |
| `won` | stake × price |
| `lost` | 0 |
| `void` | the stake back |
| `dead_heat` | stake × `dead_heat_factor` × price (the rest of the stake is lost) |

An outcome that official evidence cannot settle is left out, never guessed. A market settles on its own once every one of its outcomes is settled, so an event can have `settlement` `partial` while some markets are still pending. Treat a bet with no matching outcome as pending, not lost.

## The program

The bets here are hard-coded on a finished test game. In your code they come from your own database, with the event id you stored when the bet was placed.

```javascript
const EVENT = "ev_000ckk06psy5";
const bets = [
  { market: "winner", side: "home", line: null, stake: 10, price: 1.85 },
  { market: "handicap", side: "away", line: 3.5, stake: 20, price: 1.9 },
  { market: "total_points", side: "over", line: 45.5, stake: 10, price: 1.87 },
];

const response = await fetch(`https://api.sockodds.com/v1/results?event_id=${EVENT}`, {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
const results = (await response.json()).data;

// One result per event. It gains outcomes as more markets settle, so read it again later.
const graded = results.flatMap((result) => result.outcomes);
if (results.length) {
  const last = results[results.length - 1];
  console.log(`${last.event.name}: ${last.settlement}, markets pending: ${last.pending_markets}`);
}

for (const bet of bets) {
  const label = `${bet.market} ${bet.side}${bet.line === null ? "" : " " + bet.line} at ${bet.price.toFixed(2)}`;
  const outcome = graded.find(
    (o) => o.market.key === bet.market && o.market.period === "full" && o.side === bet.side && o.line === bet.line,
  );
  if (!outcome) {
    console.log(`${label}: pending, stake ${bet.stake.toFixed(2)} still open`);
    continue;
  }
  let returned = 0;
  if (outcome.verdict === "won") returned = bet.stake * bet.price;
  if (outcome.verdict === "void") returned = bet.stake;
  if (outcome.verdict === "dead_heat") returned = bet.stake * outcome.dead_heat_factor * bet.price;
  const profit = returned - bet.stake;
  console.log(`${label}: ${outcome.verdict}, stake ${bet.stake.toFixed(2)}, returns ${returned.toFixed(2)}, profit ${profit.toFixed(2)}`);
}
```

```python
import json, os, urllib.request

EVENT = "ev_000ckk06psy5"
bets = [
    {"market": "winner", "side": "home", "line": None, "stake": 10, "price": 1.85},
    {"market": "handicap", "side": "away", "line": 3.5, "stake": 20, "price": 1.9},
    {"market": "total_points", "side": "over", "line": 45.5, "stake": 10, "price": 1.87},
]

request = urllib.request.Request(
    "https://api.sockodds.com/v1/results?event_id=" + EVENT,
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
    results = json.load(response)["data"]

# One result per event. It gains outcomes as more markets settle, so read it again later.
graded = [outcome for result in results for outcome in result["outcomes"]]
if results:
    last = results[-1]
    print(f"{last['event']['name']}: {last['settlement']}, markets pending: {last['pending_markets']}")

for bet in bets:
    label = f"{bet['market']} {bet['side']}{'' if bet['line'] is None else ' ' + str(bet['line'])} at {bet['price']:.2f}"
    outcome = next(
        (o for o in graded
         if o["market"]["key"] == bet["market"] and o["market"]["period"] == "full"
         and o["side"] == bet["side"] and o["line"] == bet["line"]),
        None,
    )
    if outcome is None:
        print(f"{label}: pending, stake {bet['stake']:.2f} still open")
        continue
    returned = 0
    if outcome["verdict"] == "won":
        returned = bet["stake"] * bet["price"]
    if outcome["verdict"] == "void":
        returned = bet["stake"]
    if outcome["verdict"] == "dead_heat":
        returned = bet["stake"] * outcome["dead_heat_factor"] * bet["price"]
    profit = returned - bet["stake"]
    print(f"{label}: {outcome['verdict']}, stake {bet['stake']:.2f}, returns {returned:.2f}, profit {profit:.2f}")
```

Output (from a local copy of the API with invented test prices):

```text
Arizona Cardinals v Atlanta Falcons: partial, markets pending: 1
winner home at 1.85: won, stake 10.00, returns 18.50, profit 8.50
handicap away 3.5 at 1.90: lost, stake 20.00, returns 0.00, profit -20.00
total_points over 45.5 at 1.87: pending, stake 10.00 still open
```

The total points bet stays pending: that market has no graded outcome yet, so the program leaves the stake open instead of calling it lost.

## Matching a bet to an outcome

- **Store the ids when you can.** Every outcome in an odds response has an `id` (`oc_` and 16 characters), and results use the same id. If you store the outcome id with the bet, match on `id` alone. This program matches on market key, period, side and line so you can see the parts that make up an outcome.
- **Handicap lines are per side.** The away side of a home `-3.5` handicap has line `3.5`. Store the line exactly as the outcome showed it.
- **Dead heats.** A Rugby League World Cup head to head still level after golden point, and a drawn AFL or AFLW head to head, settle as `dead_heat` with a factor of 0.5: half the stake is paid at the price and half is lost.
- **Corrections.** A result's `status` is `final`, or `corrected` when a later official correction replaced it. Re-read results for recent events and settle again if the status changes to `corrected`.

## Make it yours

- Settle a whole round: call [`/v1/results`](https://sockodds.com/docs/#listResults) with `league=` and a `starts_after` and `starts_before` window, and follow `links.next`.
- Check again later: run the program on a schedule until every bet has a verdict.

See [results](https://sockodds.com/docs/results/) for the grading rules. The output above comes from a local copy of the API with an invented test game.
