Every number in probe.mjs's output comes from two non-obvious encodings. Walked here step by
step on real captured data from ../samples/demo.json. Run
node lib/decode.mjs to see the exact decoders execute.
Odds come as ×1000 fixed-point integers (avoids floats on the wire):
USA vs Belgium — 1X2 (win / draw / win)
PriceNames : ["part1", "draw", "part2"]
Prices : [2769, 3610, 2763]
Divide each by 1000:
2769 / 1000 = 2.77 ← USA win
3610 / 1000 = 3.61 ← draw
2763 / 1000 = 2.76 ← Belgium win
(2.77 means a $1 stake returns $2.77 — i.e. $1.77 profit.)
Pct is the implied probability %, as a string with 3 decimals:
Pct : ["36.114", "27.701", "36.193"] → 36.1% / 27.7% / 36.2%
Here's the key part — add them up:
36.114 + 27.701 + 36.193 = 100.008%
A normal bookmaker's implied probabilities sum to ~105% — that extra ~5% is the overround, the
bookie's built-in margin. This feed's bookmaker is TXLineStablePriceDemargined: the margin is
removed, so the probabilities sum to ~100%. That means Pct here is fair value, not a
taxed price — the genuine consensus probability. This is the single biggest reason the data is
interesting, and it's concretely visible in every 1X2 line.
(Asian handicap line=0 does the same: ["50.302", "49.677"] → 50.3% + 49.7% = 100.0%.)
Asian handicaps on quarter-ball lines (line=-0.25, line=0.25) and over/under lines don't get a
single implied % — the bet splits — so Pct is "NA":
ASIANHANDICAP line=-0.25: Prices [2397, 1716] Pct ["NA", "NA"]
→ decimal 2.40 / 1.72 (price still decodes; probability doesn't)
Stats is one flat map. Each key is (period * 1000) + baseKey:
- period:
0= total,1= H1,2= H2,3= ET1,4= ET2,5= PE - baseKey:
1/2= P1/P2 goals,3/4= yellow cards,5/6= red cards,7/8= corners
Real captured totals from Mexico vs England:
"1": 2, "2": 3, "3": 2, "4": 4, "5": 0, "6": 1, "7": 10, "8": 2
Decode each key:
| key | period | baseKey | meaning | value |
|---|---|---|---|---|
1 |
0 (total) | 1 | P1 goals | 2 |
2 |
0 | 2 | P2 goals | 3 |
3 |
0 | 3 | P1 yellow | 2 |
4 |
0 | 4 | P2 yellow | 4 |
5 |
0 | 5 | P1 red | 0 |
6 |
0 | 6 | P2 red | 1 |
7 |
0 | 7 | P1 corners | 10 |
8 |
0 | 8 | P2 corners | 2 |
→ Mexico 2 – 3 England. Mexico: 2 yellow, 10 corners. England: 4 yellow, 1 red, 2 corners.
Now the per-period keys (the thousands digit is the period):
"1001": 1 → period 1 (H1), baseKey 1 → P1 first-half goals = 1
"1002": 2 → H1, baseKey 2 → P2 first-half goals = 2
"1007": 4 → H1, baseKey 7 → P1 first-half corners = 4
"1008": 2 → H1, baseKey 8 → P2 first-half corners = 2
"2001": 1 → period 2 (H2), baseKey 1 → P1 second-half goals = 1
"3003": 2 → period 3 (ET1), baseKey 3 → P1 extra-time yellow = 2
"3006": 1 → ET1, baseKey 6 → P2 extra-time red = 1
This is what makes the data composable: the same flat map answers "total corners", "first-half corners", "second-half yellow cards" — any stat in any period. That's what lets a market settle "H1 corners > 3" trustlessly from the on-chain encoding.
node lib/decode.mjs # the decoders + a self-check on a known sample
node probe.mjs --from-samples --explain # see them applied to samples/demo.jsonField reference: data-model.md. Decoder source: ../lib/decode.mjs.