Skip to content

feat(fix): order-group lifecycle messages (closes #427)#435

Merged
TexasCoding merged 1 commit into
mainfrom
feat/427-fix-order-groups
Jun 6, 2026
Merged

feat(fix): order-group lifecycle messages (closes #427)#435
TexasCoding merged 1 commit into
mainfrom
feat/427-fix-order-groups

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

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).

OrderGroupID was already carried on NewOrderSingle / OrderCancelReplaceRequest from #424, so this PR adds only the lifecycle messages.

Messages (messages/order_groups.py)

35= Class Dir Notes
UOG OrderGroupRequest out create() / reset() / delete() / trigger() / update() helpers encode each action's required fields
UOH OrderGroupResponse in order_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 omits OrderGroupID (the server assigns it), Update requires id + limit, and optional AllocAccount (79, INT subaccount number) scopes a follow-up action to the group's owning subaccount.

async with client.order_entry(on_message=handle) as session:
    await session.send(OrderGroupRequest.create(5000))           # UOH returns the new id
    await session.send(OrderGroupRequest.update(gid, 2500))
    await session.send(OrderGroupRequest.delete(gid))
# handle(raw): decode_app_message(raw) -> OrderGroupResponse | ...

Business-logic errors (group not found, etc.) arrive as BusinessMessageReject (35=j); malformed fields as a session Reject (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.fix and kalshi.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 OrderGroupAction enum); dispatch; and a create → response → delete lifecycle integration test against the mock acceptor.

Verification

  • uv run ruff check . — clean
  • uv run mypy kalshi/ (strict) — clean (155 files)
  • uv run pytest tests/fix/182 passed

Closes #427. Part of #402.

🤖 Generated with Claude Code

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>
@claude

claude Bot commented Jun 6, 2026

Copy link
Copy Markdown

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

alloc_account coverage gap in golden wire tests

test_order_group_create_with_subaccount_golden_wire covers create(..., alloc_account=2), but there are no wire tests for alloc_account on reset, delete, trigger, or update. The tag appends at a different position for non-create actions (after the id, not after the contracts limit), so a golden wire test for at least one of them (e.g. reset(..., alloc_account=2)) would verify the field ordering is correct for those paths.

Redundant assertion in test_order_group_create_golden_wire

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 20130=, the tag is absent. The first assertion already proves it. Consider dropping line 2 to keep tests concise.

or "" fallback in lifecycle test

await session.send(OrderGroupRequest.delete(decoded.order_group_id or ""))

An empty string is not a valid OrderGroupID and would produce a silent bad request. The test already asserts decoded.order_group_id == GID two lines earlier, so id is guaranteed non-None at that point. Use an explicit assertion instead to narrow the type and fail loudly if the invariant ever breaks:

assert decoded.order_group_id is not None
await session.send(OrderGroupRequest.delete(decoded.order_group_id))

Style / Convention (CLAUDE.md)

Module docstring length

order_groups.py opens with a 14-line module docstring. CLAUDE.md says "Don't add multi-line comment blocks" and "Only add a comment when the WHY is non-obvious". The per-action field rules (Create omits OrderGroupID, subaccount scoping behavior) are genuinely non-obvious protocol constraints worth documenting, but the first two paragraphs largely re-describe the class names and PR summary. Trimming to just the non-obvious constraints (subaccount scoping, server-assigned ID on Create) would be more in line with the project style.


Questions / Edge Cases

  • contracts_limit = 0 — is that a valid input for create() / update()? The exchange may reject it with a BusinessMessageReject, but if the docs explicitly forbid it, a simple > 0 guard on the class-method signatures (not the model field) would fail fast client-side.

  • Tag 79 (ALLOC_ACCOUNT) type — the inline comment explains Kalshi types this as INT rather than STRING. Worth confirming in the Tag dictionary that the tag is already declared as INT there too, or that the codec handles it via FixType.INT in the field declaration (which it does via fixfield(Tag.ALLOC_ACCOUNT, FixType.INT, ...)). Looks fine, just noting.


Strengths

  • Helper factory methods (create, reset, delete, trigger, update) cleanly enforce per-action required fields without any conditional logic in a single constructor — good design.
  • Golden wire fixtures pinned to docs examples are exactly the right regression strategy for FIX encoding.
  • Response fields are all Optional for inbound robustness — correct approach.
  • Dispatch registration (MsgType.ORDER_GROUP_RESPONSE) is present; outbound-only OrderGroupRequest is correctly omitted from the registry.
  • Exports wired correctly at all three levels (messages/__init__.py, fix/__init__.py, __all__).

Verdict: Approve with the lifecycle test or "" fix and the alloc_account wire coverage addressed. The redundant assertion and docstring trim are minor polish.

@TexasCoding
TexasCoding merged commit 8fdce1a into main Jun 6, 2026
5 checks passed
@TexasCoding
TexasCoding deleted the feat/427-fix-order-groups branch June 6, 2026 11:49
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.

[FIX] Order Groups (prediction + perps)

1 participant