Metadata-Version: 2.5
Name: oak-domain-betting
Version: 0.3.0
Summary: Betting domain for the OakQuant platform. Point-in-time odds capture first — the one asset in the programme that cannot be backfilled.
Author-email: Pumulo Sikaneta <pumulo@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.13
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: forecast
Requires-Dist: oak-forecast<1.0.0,>=0.1.0; extra == 'forecast'
Provides-Extra: research
Requires-Dist: anthropic<1.0,>=0.40; extra == 'research'
Requires-Dist: cambium-ai[claude,gemini]>=0.4.0; extra == 'research'
Requires-Dist: nflreadpy>=0.1.5; extra == 'research'
Requires-Dist: polars>=1.0; extra == 'research'
Provides-Extra: weekend
Requires-Dist: psycopg[binary]>=3.1; extra == 'weekend'
Requires-Dist: timber-common<2.0.0,>=1.2.0; extra == 'weekend'
Description-Content-Type: text/markdown

# oak-domain-betting

The betting domain for the OakQuant platform. Right now it is one thing only:
**a point-in-time odds capture that runs from day one.**

```bash
pip install -e .
oak-odds snapshot      # every open market on every venue, timestamped
oak-odds status        # what the corpus holds
```

## Signing in

The domain is capability-gated, fail-closed: a tenant whose enterprise does not
carry `betting` sees none of it, and never another domain's content instead.

```bash
# inside grove, after the package is installed
python -m oak_domain_betting.provisioning.seed_betting_owner --dry-run
python -m oak_domain_betting.provisioning.seed_betting_owner --password-stdin
```

Creates the `oakquant-betting` enterprise, grants it the `betting` capability,
and provisions `owner.betting@oakquant.ai` as its owner. Idempotent — re-running
never duplicates a row and never deletes one.

⚠️ **Never pass a password as an argument.** It lands in the process table, in
shell history, and in any transcript. `--password-stdin` reads from a pipe or a
TTY prompt and never renders it. Omit the flag and the owner stays on the invite
flow.

## Why capture comes before the model

Everyone has results. Almost nobody has *timestamped odds* — and without them
there is no point-in-time price, no closing line value, and no honest backtest.
An hour of prices not captured cannot be reconstructed at any price. This is the
same irreversibility that already cost this platform its macro board history, and
it is the only part of the programme that has a deadline of *now*.

Everything else can be written later against the history this is accumulating
today: the de-vig, the hierarchy, the features, the gates.

## What it does not do

**It does not convert a price into a probability.** Removing the overround is
Phase B0, and proportional, Shin and power methods disagree materially on
longshots. Get it wrong and every number downstream is wrong — silently, and in
the flattering direction. So nothing here pretends to have done it.

`Quote.mid` exists and is documented as *not a probability*. Polymarket's
`outcomePrices` already sum to 1 and are deliberately **not** promoted to the
quote, because a field that looks finished is more dangerous than one that
obviously is not.

## The venues, and why these two

| venue | why |
|---|---|
| **Kalshi** | A regulated exchange, not a risk desk — you are matched against other participants, so winning does not get you closed. Carries real sports (EPL, La Liga, Europa League) as binary markets **with a settlement field**, so one capture eventually yields both the price and the label. |
| **Polymarket** | A second independent book on overlapping events. Not redundancy — a control. Two venues disagreeing is information; it is also the only way to notice a feed has gone stale, which from inside one feed looks like a market that stopped moving. |

Both are public and key-free. Nothing here has been paid for, and nothing will be
until the retention and use-class terms have been read — a monthly price reads
like a purchase and is not one, and a personal-use tier does not cover a billing
product.

⭐ **Three-way soccer arrives as three binary markets sharing one event ticker.**
`KXEPLGAME-26SEP20FULMUN` has a `-FUL`, a `-MUN` and a `-TIE`, and their three YES
mids sum to more than 1. That excess is the exchange's overround, sitting in the
data, which makes **Gate B0** testable the moment the instrument is written: a
harness that cannot reproduce the venue's own cut from quoted prices cannot be
trusted to find an edge.

## What lands on disk

```
$OAK_ODDS_DIR/                       # default ~/oakquant-odds
  raw/<provider>/<date>/<ts>.json.gz    # verbatim. THIS is the asset.
  canonical/<date>.jsonl                # flat rows. Derived, regenerable.
```

Raw is written **before** anything is parsed. If normalisation throws, the
irreplaceable half is already safe. Normalisation can be rewritten a year from now
and re-run over raw; a field dropped at capture time is simply gone.

## ⚠️ A skip is not a pass

Five silent losses shipped on this platform in one week, every one the same shape:
a loop that skipped records and reported success. So the capture counts what was
**written**, not what was attempted; every skipped market carries a reason;
truncation from hitting a page limit is recorded differently from a feed that
simply ended; and a run that captures nothing exits non-zero and says so —
"NOTHING CAPTURED. This hour is lost and cannot be refetched."

Apache-2.0.

## The weekend briefing (local only)

`oak-betting-weekend` turns a weekend of NFL games into a briefing and a paper
ledger. It is a recorded prediction, not a pick: there is no model (Gate B2 has
not started), so with the shipped policy every game is **No bet**, and the
briefing says so as a finished answer.

```bash
pip install -e '.[weekend]'
oak-betting-weekend brief                     # this weekend; fresh Kalshi snapshot first
oak-betting-weekend brief --estimate GB=0.45  # record your own probability for a team
oak-betting-weekend record-bet 401872990 --stake 10 --price 43
oak-betting-weekend settle                    # scores + closing-line value, after the games
```

- **Market line**: Kalshi moneyline from the odds corpus, mid-normalised. The
  bar a bet must clear is the ask plus Kalshi's fee, not the mid.
- **Judgments**: one `common.services.judgment` call per game over that game's
  ESPN injury report and team news, filtered in code. Questions live in
  `weekend/questions.py`; every one passes timber's guards with zero findings.
- **Policy**: `weekend/policy.yaml`. `weights` is empty on purpose.
- **Ledger**: `~/oakquant-betting/ledger/paper-ledger.jsonl` (`OAK_BETTING_DIR`),
  append-only; only the `outcome` block is ever filled in later.
- **Key**: `TYPESAFE_API_KEY` is fetched from local ranger with grove's canopy
  client credentials. Nothing goes in your shell.
- **Schedule**: `deploy/com.oakquant.betting-{brief,settle}.plist`.
