The Odds Gap Back To The Record

How To Verify The Signal Record

The record does not ask for trust. Every settled play date is hashed, each hash is chained to the one before it, and the chain is published where we cannot quietly rewrite its history.

What a hash chain does, and what it does not

It cannot stop us editing a result. It makes an edit impossible to hide. Change one graded play in a sealed day and that day's hash changes, every link after it stops reproducing, and the break points at the exact date. That is the entire claim, and it is worth making because the alternative is asking you to believe a solo operator did not delete his losers.

Late corrections are real, so they are handled openly rather than pretended away. A box score that arrives after a day is sealed produces a new link at a higher revision with the earlier link left in place. An amendment is legitimate. An invisible one is not.

Current state

Ruleset v1.0 · chain starts 2026-07-25 · 14 links · status INTACT
Tip: 91510ec97bf65c00ab9b534ccd2e7ed95cc6b615b15aca319ee15dc3db1fab16

The exact format

One line per settled play, ordered by play id. Each line is a compact JSON array with these fields in this order, and nothing else:

id, play_date, flagged_at, kind, sport, market, side, line, book, odds, score, tier, ruleset_version, result, units, closing_fair_prob, clv_pp

Real numbers are rendered as fixed four-decimal strings and whole numbers stay whole, so a verifier written in any language reproduces the same bytes rather than inheriting one language's float formatting. A missing value is null, never an empty string, because a missing price and a zero price are different facts. Lines are joined with newlines and the text ends with a newline.

rows_hash = sha256(that text)
chain_hash = sha256(prev_hash + "\n" + rows_hash), with prev_hash empty for the first link.

The endpoints

Both are public and unauthenticated. A verification mechanism only we can run is not a verification mechanism.

Check it yourself

Standard library only, no packages to install. Python 3.

import hashlib, json, urllib.request

BASE = "https://theoddsgap.com"

def get(path):
    with urllib.request.urlopen(BASE + path) as r:
        return json.load(r)

chain = get("/api/signal-log-hashes")
prev = ""
for link in chain["links"]:
    day = get("/api/signal-log-rows?date=" + link["play_date"])
    rows_hash = hashlib.sha256(day["text"].encode()).hexdigest()
    chain_hash = hashlib.sha256((prev + "\n" + link["rows_hash"]).encode()).hexdigest()
    latest = not link["superseded"]
    ok_rows = (rows_hash == link["rows_hash"]) or not latest
    ok_link = chain_hash == link["chain_hash"]
    print(link["play_date"], "rev", link["revision"],
          "rows", "OK" if ok_rows else "MISMATCH",
          "link", "OK" if ok_link else "MISMATCH")
    prev = link["chain_hash"]

A superseded revision is expected not to match the rows as they stand today, because it describes the ledger as it was before an amendment. That is exactly what it is published for.

Every link

Play dateRevPlaysRows hashChain hashSealedState
2026-07-25 1 513 77d37c229028a005 e94e282785b4bbdb 2026-08-10 12:20:01 verified
2026-07-26 1 505 c9de83bebe8080ca baf8bbcd4e3b08e3 2026-08-10 12:20:01 verified
2026-07-27 1 561 6ec7b721121f622b 61e8d60fa9cbd727 2026-08-10 12:20:01 verified
2026-07-28 1 1182 eeedfe0921ffbd07 b929e1af7bbb74df 2026-08-10 12:20:01 verified
2026-07-29 1 508 c11ff8a3d3394c43 6e58ee7d40907d97 2026-08-10 12:20:01 verified
2026-07-30 1 1107 bdbd766efd3b101c 15ac8379cb8c7f1f 2026-08-10 12:20:01 verified
2026-07-31 1 1160 60a13ce13c924ab7 8e5dcdce18af6662 2026-08-10 12:20:01 verified
2026-08-01 1 1132 89577a23b021564b 2c66f3b9a026ad9d 2026-08-10 12:20:01 verified
2026-08-02 1 1121 329345b40ef702c7 f4a4769adc4c19e7 2026-08-10 12:20:01 verified
2026-08-03 1 1166 a1d015919e5d801b 87e644251b5cda93 2026-08-10 12:20:01 verified
2026-08-04 1 1152 41953ee6d1e20a69 cc30aed2e378f758 2026-08-10 12:20:01 verified
2026-08-05 1 1170 07ae786b9b9f33a9 b2c63b9940bb51ae 2026-08-10 12:20:01 verified
2026-08-06 1 1124 d4d8ebff988634a1 1befc32d5b27bec7 2026-08-10 12:20:01 verified
2026-08-07 1 1129 42a0cc5771e6d437 91510ec97bf65c00 2026-08-10 12:20:01 verified

The record does not ask for trust.