Skip to content

orders: assert outside-RTH on a replace from the resubmitted order (ibx#247) - #249

Open
userFRM wants to merge 1 commit into
deepentropy:mainfrom
userFRM:fix/modify-outside-rth
Open

orders: assert outside-RTH on a replace from the resubmitted order (ibx#247)#249
userFRM wants to merge 1 commit into
deepentropy:mainfrom
userFRM:fix/modify-outside-rth

Conversation

@userFRM

@userFRM userFRM commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • The replace hard-coded 6433=1 (src/engine/hot_loop/order_builder.rs, Modify arm) with a comment claiming it preserved the original. It cannot: types::Order (src/types.rs:251-266), the record the replace is rebuilt from, has no outside-RTH field.
  • Placement emits 6433 only when the caller sets it, so an order placed with outside_rth=false — the default — was opted into the extended session by its first modify. For a resting stop that is the difference between triggering in RTH only and triggering on a thin extended-hours print. It hits every modified order, not only bracket children.
  • OrderRequest::Modify now carries the flag from the resubmitted order and the replace emits 6433 only when set.
  • Closes orders: a modify sends 6433=1 unconditionally, so any order placed with outside_rth=false becomes outside-RTH on first modify #247.

Why the caller's order rather than the cached one

track_order overwrites the cached ApiOrder with the caller's order at the end of every place_order, modify branch included (src/client_core.rs:768-773), so the cache holds the previous resubmission. Reading it would use a stale flag and ignore an explicit change on this call. Price and qty already come from the resubmitted order, and place_order with a tracked id is the documented modify entry point, so caller-supplied is both the coherent source and the consistent one.

Wire

With the flag set the message is unchanged — 6433 keeps its captured position between 6122 and 38. With it clear, the absent tag is the only difference. The conditional is a literal split rather than a positional insert, so the ordering is correct by construction.

Test

Nothing in-tree exercised the Modify arm's encoder, which is why a hard-coded tag survived there. modify_emits_outside_rth_only_when_the_caller_set_it drives the arm over a loopback socket and asserts |6122=c|6433=1|38=50| when the flag is set and no 6433 when clear. Reintroducing the unconditional tag fails it; moving the tag to the tail fails it on the positional assertion.

cargo test --lib — 804 pass. The two config::expiry_tests failures and the ib_paper_compat E0063s are present on main before this branch; the six Modify literals in that target are updated so this adds no new errors.

Scope and caveats

Breaking: OrderRequest::Modify gains a field and Context::modify gains an argument, both public. EClient::place_order and the Python signatures are unchanged. That reads like the 0.8.0 lane rather than 0.7.2 — your call.

What this does not claim: I have no capture for how the gateway treats an absent 6433 on a 35=G. Under either reading the bug this targets is fixed — an RTH-only order can no longer come back extended-hours-enabled, and an explicit true still asserts the tag. The open case is placed-true then modified with false: if absence means "unchanged" rather than "false", the order stays outside-RTH. I could not settle that on paper, because the only readback path is req_open_orders, which serves local state that track_order has already overwritten with the caller's own values — so it echoes the input rather than reporting the gateway.

Test plan

  • cargo test --offline --lib — 804 passed. The 2 failures are config::expiry_tests::{named_zone_converts_with_dst, instant_round_trips_to_wire}, which fail on the base commit too: the host has no legacy timezone files (fixed separately in config: resolve the legacy timezone names IB states its times in (ibx#335) #336).
  • cargo check --offline --features python — clean.
  • Mutation check: reverting the conditional 6433 fails modify_emits_outside_rth_only_when_the_caller_set_it by name.

🤖 Generated with Claude Code

…bx#247)

The replace hard-coded tag 6433=1 with a comment claiming it preserved the original. It could not: the tracked `types::Order` the replace is rebuilt from carries no outside-RTH field. Placement emits 6433 only when the caller asks for it, so an order placed with `outside_rth=false` — the default — was opted into the extended session by its first modify. For a resting stop that is the difference between triggering in RTH only and triggering on a thin extended-hours print.

`OrderRequest::Modify` now carries the flag from the order the caller resubmitted, and the replace emits 6433 only when it is set. The caller's order is the right source rather than the cached one: `track_order` overwrites the cache with the resubmitted order at the end of every `place_order`, modify included, so the cache holds the previous resubmission and reading it would ignore an explicit change on this call. Price and qty already flow this way.

With the flag set the emitted message is unchanged, 6433 keeping its captured position between 6122 and 38; with it clear the tag is the only thing absent. A test drives the Modify arm over a loopback socket and pins both polarities at the wire, since nothing in-tree exercised that encoder before — which is why the hard-coded tag survived.

`Context::modify` takes the flag for the same reason, and `OrderRequest::Modify` gains a field, so this is a source-breaking change for direct constructors of either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

orders: a modify sends 6433=1 unconditionally, so any order placed with outside_rth=false becomes outside-RTH on first modify

1 participant