feat(fix): drop-copy message flow + listener sessions (closes #425)#433
Conversation
Kalshi drop copy is a request/response history query (identical on both products), not a streaming feed. - drop_copy.py: EventResendRequest (35=U1, outbound), EventResendComplete (35=U2) and EventResendReject (35=U3, inbound); EventResendRejectReason enum - centralize inbound dispatch in a new messages/dispatch.py (APP_MESSAGE_MODELS + decode_app_message) spanning the order-entry and drop-copy message types; moved out of order_entry.py and re-exported unchanged from kalshi.fix / kalshi.fix.messages (so the registry scales as later flows land) - listener (read-only streaming) sessions: FixConfig.listener_session + skip_pending_exec_reports, plumbed into the Logon; listener_session requires skip_pending_exec_reports (validated in __post_init__) Usage: send EventResendRequest on a KalshiDC session; the gateway replays the matching ExecutionReports (35=8) terminated by EventResendComplete (U2) or EventResendReject (U3) — decode via decode_app_message. 8 new tests: U1/U2/U3 round-trips, dispatch for the drop-copy types, a DC query session-integration test, listener logon-flag emission, and the listener-requires-skip validation. ruff + mypy --strict clean; 131 FIX tests pass. Closes #425. Part of #402. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review — feat(fix): drop-copy message flow + listener sessionsOverviewClean, well-structured PR. The main architectural decision — pulling Issues1. Potential race condition in await session.start()
logon = acceptor.first("A") # synchronous — no until()
assert logon is not NoneEvery other integration test in the file uses await session.start()
await until(lambda: acceptor.first("A") is not None)
logon = acceptor.first("A")2.
assert back.event_resend_reject_reason == EventResendRejectReason.BEGIN_EXECID_TOO_SMALLThis passes only because assert back.event_resend_reject_reason == 3 # EventResendRejectReason.BEGIN_EXECID_TOO_SMALLLow-priority observations3.
4. The note "See GH #432 for surfacing decode failures to consumers" is a useful breadcrumb — just make sure that issue exists and is open. 5. No validation of the
What is done well
SummaryTwo actionable items: fix the potential race in |
- test_listener_logon_emits_flags: poll the acceptor for the Logon via until() instead of reading it synchronously. The test was already deterministic (the mock acceptor records the Logon before responding, and start() returns only after the Logon round-trips), but this matches the rest of the file's idiom and stays robust to future acceptor changes. - test_listener_session_requires_skip_pending: also assert FixConfig.margin( listener_session=True) raises, closing the factory-coverage gap (margin() feeds the same __post_init__ validator as prediction()). Review item 2 (event_resend_reject_reason assertion style) intentionally not changed: asserting `== EventResendRejectReason.BEGIN_EXECID_TOO_SMALL` matches the established convention (test_order_entry.py asserts `back.exec_type == ExecType.TRADE` on the same raw-int/enum cross-equality pattern); switching to `== 3` would make drop-copy inconsistent with order-entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code ReviewOverviewPR adds the drop-copy FIX flow ( Tests cover the round-trips, dispatch, the session-integration scenario, and the config validator. Findings1.
|
Summary
Drop-copy FIX flow plus listener-session support. Kalshi drop copy is a request/response history query (identical on both products), not a streaming feed — for a live stream you use a listener session on KalshiRT. Builds on the foundation (#422), groups (#423), and order entry (#424).
What's included
messages/drop_copy.py—EventResendRequest(35=U1, outbound:BeginExecIDrequired,EndExecIDoptional),EventResendComplete(35=U2) andEventResendReject(35=U3) inbound; newEventResendRejectReasonenum (1 rate-limited / 2 server-error / 3 begin-too-small / 4 end-too-large).messages/dispatch.py— the inbound dispatch registry (APP_MESSAGE_MODELS) +decode_app_messagemoved here fromorder_entry.pyand extended to the drop-copy types, so it spans message flows cleanly (re-exported unchanged fromkalshi.fixandkalshi.fix.messages— no public-API change).FixConfig.listener_session+skip_pending_exec_reports(read-only streaming execution reports on NR/RT), plumbed into the Logon.listener_session=Truerequiresskip_pending_exec_reports=True(validated at construction).Usage
Resent reports carry new FIX sequence numbers (reconcile by the compound
"int;int"ExecID); 3-hour lookback; rejects/pending (-1;-1) excluded — per the Kalshi docs.Tests (
tests/fix/test_drop_copy.py, 8 new)U1/U2/U3 round-trips (incl. begin-only request),
decode_app_messagedispatch for the drop-copy types, a DC query session-integration test (sendEventResendRequest→ receive replayedExecutionReport+EventResendComplete), listener logon-flag emission, and the listener-requires-skip validation.Verification
uv run ruff check .— cleanuv run mypy kalshi/(strict) — clean (152 files)uv run pytest tests/fix/— 131 passedCloses #425. Part of #402.
🤖 Generated with Claude Code