Skip to content

fix(feed): Kalshi changed its wire format and the books went quietly empty - #69

Merged
DanielWLiu07 merged 2 commits into
mainfrom
fix/kalshi-wire-schema
Sep 6, 2026
Merged

DanielWLiu07 merged 2 commits into
mainfrom
fix/kalshi-wire-schema

Conversation

@DanielWLiu07

Copy link
Copy Markdown
Owner

Kalshi's orderbook_delta channel now sends decimal dollar strings under new
field names. Snapshots carry yes_dollars_fp / no_dollars_fp holding
["0.5100", "1200.50"] pairs; deltas carry price_dollars / delta_fp with a
signed, fractional size. The parser asked for yes / no and get_int64.

Why this is a fix and not a feature

A missing side key was treated as "a legal empty book", so every snapshot
parsed successfully into nothing:

before:  kalshi 10 msgs    6 deltas   0 malformed   0 gaps
after:   kalshi 11 msgs  240 deltas   0 malformed   0 gaps

Books empty, malformed zero, integrity green. Throughput and integrity counters
structurally cannot see this - only reading prices out of the book can. It was
found by pointing the engine at production, not by any test in the suite.

What changed

Both wire forms are accepted, so the captures under docs/bench/ still replay
byte for byte and all 250 existing tests pass unchanged.

  • Prices reuse parse_cents, whose OffGrid rejection refuses to round a
    sub-cent price (Kalshi now lists some markets on a 0.0001 grid) into a
    neighbouring cent it can represent.
  • Sizes get to_rounded_contracts: signed, because a resting level shrinks as
    well as grows, and rounded to whole contracts rather than kSizeScale,
    because a Kalshi contract is countable, not a fractional base unit.
  • An unrecognised snapshot is now Malformed. An empty Kalshi book still sends
    its side keys holding empty arrays, so keys absent under every known name
    means a schema this parser does not know.

Validation

Five new tests cover the new shape, the old one, empty-but-legal books, and
sub-cent rejection. The regression guard was fault-injected: removing
!saw_side fails SnapshotWithNoRecognisedSideKeyIsMalformed specifically,
so the test can actually fail.

Verified live against the September 2026 FOMC decision quoted on both venues
(both closing 2026-09-16), which now produces a real cross-venue basis:

event fed-sep-2026-no-change
  basis    mean -7.21c  sd 7.98c  (90 samples)
           mean-reverting (ar1 0.973), half-life 25.1 updates
  spread   kalshi 7.76c  polymarket 13.41c  -- basis within the spread (noise)
  arb      0/90 two-sided updates crossable (0.00%)

Also here

chore(security) adds content-based secret scanning. .gitignore covered
secrets/, which protects a path; a key pasted into a markdown note committed
cleanly. Both the key-in-.md and key-id-in-.cpp cases are fault-injected.

…empty

Kalshi's orderbook_delta channel now sends decimal dollar strings under
new field names: snapshots carry yes_dollars_fp / no_dollars_fp holding
["0.5100", "1200.50"] pairs, and deltas carry price_dollars / delta_fp
with a signed, fractional size. The parser asked for yes / no and
get_int64, so nothing matched.

The failure mode is the reason this is a fix and not a feature. A missing
side key was treated as "a legal empty book", so every snapshot parsed
successfully into nothing at all: books empty, malformed counter zero,
integrity checks green. A live capture reported 0 malformed and 6 deltas
where it should have had 240. Throughput and integrity counters cannot
see this - only reading prices out of the book can.

Both wire forms are accepted so the captures under docs/bench/ still
replay byte for byte. Prices reuse parse_cents, whose OffGrid rejection
refuses to round a sub-cent price (Kalshi now lists some markets on a
0.0001 grid) into a neighbouring cent it can represent. Sizes get
to_rounded_contracts: signed, because a resting level shrinks as well as
grows, and rounded to whole contracts rather than kSizeScale, because a
Kalshi contract is a countable thing and not a fractional base unit.

An unrecognised snapshot is now Malformed rather than empty. An empty
Kalshi book still sends its side keys holding empty arrays, so keys
absent under every known name means a schema this parser does not know,
and saying so loudly is the whole point.
…ands

.gitignore covered secrets/, which protects a path and nothing else. A key
pasted into a scratch file at the root, or into a markdown note, committed
cleanly - and that is exactly how keys reach public history.

scripts/check_no_secrets.py scans content instead of paths: private key
blocks under any PEM label, and the exact value of any credential sitting
in secrets/, since a bare UUID is too generic to blocklist by shape. It
runs over the staged set from scripts/git-hooks/pre-commit and over every
tracked file standalone, so CI catches what a clone without
core.hooksPath would miss.

Both cases are fault-injected rather than assumed: a key pasted into
docs/notes_scratch.md and a key id pasted into src/cli/usage.cpp were each
rejected with HEAD unmoved. The .md case is the one that matters, because
the extension rules below would have waved it straight through.

Enable in a fresh clone with:
    git config core.hooksPath scripts/git-hooks
@DanielWLiu07
DanielWLiu07 merged commit 227042b into main Sep 6, 2026
17 of 18 checks passed
@DanielWLiu07
DanielWLiu07 deleted the fix/kalshi-wire-schema branch September 6, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant