feat(fix): order-group lifecycle messages (closes #427)#435
Conversation
Order groups ride the order-entry session (no dedicated session type) for automatic position-limit management. OrderGroupID was already carried on NewOrderSingle/OrderCancelReplaceRequest (#424); this adds the lifecycle. - enums: OrderGroupAction (20131, INT) — CREATE/RESET/DELETE/TRIGGER_CANCEL/UPDATE - messages/order_groups.py: - OrderGroupRequest (35=UOG, outbound) with create()/reset()/delete()/trigger() /update() helpers encoding each action's required fields (Create omits OrderGroupID — the server assigns it; Update needs id + limit). Optional AllocAccount (79, INT subaccount) scopes the action to a subaccount's group. - OrderGroupResponse (35=UOH, inbound); fields optional for inbound robustness (contracts-limit echoed only on Create/Update). Business errors arrive as a BusinessMessageReject (35=j), malformed fields as a session Reject (35=3). - dispatch registers UOH; exports from kalshi.fix / kalshi.fix.messages. All tags (20130/20131/20132/79) and MsgTypes (UOG/UOH) already existed from the foundation; field order follows the docs examples (action, id, limit, account). 15 new tests: golden exact-wire fixtures for all five actions + the response, round-trips, dispatch, and a create->response->delete lifecycle integration test against the mock acceptor. ruff + mypy --strict clean; 182 FIX tests pass. Closes #427. Part of #402. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review — feat(fix): order-group lifecycle messages (#427)Overall: Clean, minimal PR that follows the existing FIX message patterns well. The helper class methods elegantly encode per-action field requirements, the golden wire tests are solid, and the lifecycle integration test is thorough. A few minor items below. Correctness / Logic
Redundant assertion in assert _wire(msg) == "20131=1|20132=5000"
assert int(Tag.ORDER_GROUP_ID) not in {t for t, _ in msg.to_body_fields()}The second assertion is redundant — if the wire string doesn't contain
await session.send(OrderGroupRequest.delete(decoded.order_group_id or ""))An empty string is not a valid assert decoded.order_group_id is not None
await session.send(OrderGroupRequest.delete(decoded.order_group_id))Style / Convention (CLAUDE.md)Module docstring length
Questions / Edge Cases
Strengths
Verdict: Approve with the lifecycle test |
Summary
FIX Order Groups for both products — the create/reset/delete/trigger/update group lifecycle. Order groups ride the order-entry session (no dedicated session type) for automatic position-limit management. Builds on the foundation (#422) and order entry (#424).
OrderGroupIDwas already carried onNewOrderSingle/OrderCancelReplaceRequestfrom #424, so this PR adds only the lifecycle messages.Messages (
messages/order_groups.py)OrderGroupRequestcreate()/reset()/delete()/trigger()/update()helpers encode each action's required fieldsOrderGroupResponseorder_group_id/order_group_contracts_limit/alloc_account(limit echoed only on Create/Update)New enum
OrderGroupAction(20131, INT):CREATE=1/RESET=2/DELETE=3/TRIGGER_CANCEL=4/UPDATE=5. The helpers encode the non-obvious per-action rules from the docs — Create omitsOrderGroupID(the server assigns it), Update requires id + limit, and optionalAllocAccount(79, INT subaccount number) scopes a follow-up action to the group's owning subaccount.Business-logic errors (group not found, etc.) arrive as
BusinessMessageReject(35=j); malformed fields as a sessionReject(35=3) — both already handled by the session/dispatch layers.All tags (20130/20131/20132/79) and MsgTypes (UOG/UOH) already existed from the foundation. UOH is registered in the inbound dispatch registry; both classes are exported from
kalshi.fixandkalshi.fix.messages.Tests (
tests/fix/test_order_groups.py, 15 new)Golden exact-wire fixtures pinned to the docs examples for all five actions + subaccount-scoped create + the response; round-trips (incl. the
OrderGroupActionenum); dispatch; and acreate → response → deletelifecycle integration test against the mock acceptor.Verification
uv run ruff check .— cleanuv run mypy kalshi/(strict) — clean (155 files)uv run pytest tests/fix/— 182 passedCloses #427. Part of #402.
🤖 Generated with Claude Code