# favcohort data prep (2026-09-15) — labels only, no charts

Mirror only (`D:\work\timeportal\polymarket`). No commits. No page edits.

**Release id: `step1-chrono-3-f787137b1ea71313`** (was `step1-chrono-3-a9411d817efc4bfa`).
The family `favcohort` was added to `odds_data/insight_records.json` (494 match rows) and to the sibling file `odds_data/favcohort.json` (988 side rows, two per match). Generated 2026-09-15T04:43:17.315Z. Counts below are read from that published sibling file.

---

## 1. Definitions

Every match has two sides: the tracked side (the side whose price the recorded tape carries; `tape.favorite` / `ladderfit.fav`) and the other side (`opp`).

**TP favourite** = the side TimePortal picked at decision time. Source of truth: the bot's recorded basket stamp (`basket` in ladderfit / the bundle). `TP_ONLY` and `AGREED` mean the tracked side was TP's pick. `MARKET_ONLY` / `NONFAV` / null mean TP made no pick on the tracked side. A TP pick exists on at most one side; if the stamp says TP picked nobody, no side is TP favourite. The tour-rank proxy is not this label; it is recorded separately as `tp_pick_proxy_side` for comparison only.

**Market favourite** = the side priced at or above 50¢ at the opening price of the tracked side (the other side's price is 100¢ minus it). Exactly one side unless the opening price is exactly 50¢ or not recorded, in which case no side is market favourite (`market_fav_side = null`, reason printed).

Each side then gets exactly one **favcohort** label:

| TP favourite? | Market favourite? | favcohort |
|---|---|---|
| yes | yes | `tp_and_market_fav` |
| yes | no | `tp_only_fav` |
| no | yes | `market_only_fav` |
| no | no | `nobodys_fav` |

When one side is `tp_and_market_fav` the other side is always `nobodys_fav`. When the tracked side is `tp_only_fav` the other side is `market_only_fav` (if a market favourite exists) or `nobodys_fav` (if the open is 50¢ or missing). The match pair is `favcohort_pair` (tracked vs other).

Win rate below is `won / (won + lost)`. Void and not finished are counted as sides but are not in the denominator. A row with `won + lost = 0` has no win rate (not 0).

---

## 2. Matches by `favcohort_pair`

TOTAL = 494.

| favcohort_pair | matches | of TOTAL |
|---|---:|---|
| tp_only_fav vs market_only_fav | 274 | 274 of 494 |
| tp_and_market_fav vs nobodys_fav | 110 | 110 of 494 |
| nobodys_fav vs market_only_fav | 47 | 47 of 494 |
| tp_only_fav vs nobodys_fav | 37 | 37 of 494 |
| market_only_fav vs nobodys_fav | 22 | 22 of 494 |
| nobodys_fav vs nobodys_fav | 4 | 4 of 494 |
| **sum** | **494** | **494 of 494** |

---

## 3. Per favcohort (each side counted once)

988 sides = 494 matches × 2.

### Pooled (all sports)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 110 | 59 | 48 | 2 | 1 | 59/107 |
| tp_only_fav | 311 | 111 | 192 | 4 | 4 | 111/303 |
| market_only_fav | 343 | 210 | 129 | 3 | 1 | 210/339 |
| nobodys_fav | 224 | 101 | 112 | 7 | 4 | 101/213 |
| **sum** | **988** | **481** | **481** | **16** | **10** | |

481 won + 481 lost = 962 resolved sides. 16 void sides = 8 void matches × 2. 10 not-finished sides = 5 not-finished matches × 2. 962 + 16 + 10 = 988.

### tennis (473 matches, 946 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 105 | 57 | 45 | 2 | 1 | 57/102 |
| tp_only_fav | 299 | 108 | 183 | 4 | 4 | 108/291 |
| market_only_fav | 329 | 200 | 125 | 3 | 1 | 200/325 |
| nobodys_fav | 213 | 95 | 107 | 7 | 4 | 95/202 |

### mlb (12 matches, 24 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 5 | 2 | 3 | 0 | 0 | 2/5 |
| tp_only_fav | 5 | 2 | 3 | 0 | 0 | 2/5 |
| market_only_fav | 7 | 4 | 3 | 0 | 0 | 4/7 |
| nobodys_fav | 7 | 4 | 3 | 0 | 0 | 4/7 |

### mma (5 matches, 10 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| tp_only_fav | 5 | 1 | 4 | 0 | 0 | 1/5 |
| market_only_fav | 5 | 4 | 1 | 0 | 0 | 4/5 |
| nobodys_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |

### npb (2 matches, 4 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| tp_only_fav | 2 | 0 | 2 | 0 | 0 | 0/2 |
| market_only_fav | 1 | 1 | 0 | 0 | 0 | 1/1 |
| nobodys_fav | 1 | 1 | 0 | 0 | 0 | 1/1 |

### boxing (1 match, 2 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| tp_only_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| market_only_fav | 1 | 1 | 0 | 0 | 0 | 1/1 |
| nobodys_fav | 1 | 0 | 1 | 0 | 0 | 0/1 |

### golf (1 match, 2 sides)

| favcohort | sides | won | lost | void | not finished | win rate won/(won+lost) |
|---|---:|---:|---:|---:|---:|---|
| tp_and_market_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| tp_only_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| market_only_fav | 0 | 0 | 0 | 0 | 0 | undefined (won+lost = 0) |
| nobodys_fav | 2 | 1 | 1 | 0 | 0 | 1/2 |

---

## 4. Basket stamp vs rank proxy

Compared fields: `tp_fav_side` (from the basket stamp) and `tp_pick_proxy_side` (from the existing pivot family). `agree` = both name the same side. `disagree` = both name a side and they differ. `unavailable` = either is null.

| stamp_proxy_comparison | matches |
|---|---:|
| agree | 121 |
| disagree | 200 |
| unavailable | 173 |
| **sum** | **494** |

First 10 disagree ids (file order): `1002536`, `1000982`, `1000882`, `1000866`, `999334`, `997102`, `995693`, `989095`, `990476`, `995680`.

---

## 5. Matches with no market favourite

41 of 494. `market_fav_side = null`.

**Open exactly 50¢** (10): `959066`, `972820`, `954037`, `909565`, `851873`, `794291`, `783617`, `775339`, `768386`, `757491`.

**Open not recorded** (31): `946588`, `876329`, `878131`, `876625`, `860170`, `809257`, `804428`, `794263`, `794227`, `783599`, `784611`, `783642`, `784620`, `779911`, `771074`, `771053`, `765509`, `765507`, `760643`, `760637`, `757619`, `759455`, `757495`, `757531`, `757484`, `757597`, `757481`, `750783`, `733287`, `725781`, `720205`.

One other missing field: match `725781` (golf) has `other_side_label = not recorded` (`ladderfit.opp` is missing). Tracked-side labels and scheduled starts are present on all 494 matches.

---

## 6. Tests

`tests/favcohort.test.py`: 34 tests, 34 passed.

Covered: all four labels; the pair rule (`tp_and_market_fav` → other is `nobodys_fav`; `tp_only_fav` pairs with `market_only_fav` or `nobodys_fav`); open exactly 50¢; open missing; void; not finished; won/lost winning-side favcohort; stamp-vs-proxy agree / disagree / unavailable; `MARKET_ONLY` / `NONFAV` / null stamps as no TP pick; two side rows per match; `insight_records.analyze_match` / `records_for_match` publish the family.
