ccp: report the orders a reconnect does not explain (ibx#251) - #362
Open
userFRM wants to merge 1 commit into
Open
ccp: report the orders a reconnect does not explain (ibx#251)#362userFRM wants to merge 1 commit into
userFRM wants to merge 1 commit into
Conversation
userFRM
force-pushed
the
fix/reconnect-reconciliation
branch
from
July 30, 2026 14:51
8f93d0d to
d61378f
Compare
userFRM
force-pushed
the
fix/reconnect-reconciliation
branch
from
July 31, 2026 11:33
d61378f to
d56cb83
Compare
An order working when the connection drops is marked Uncertain, and that was the end of it. The engine stopped believing the status and told nobody, so the API layer went on reporting the pre-disconnect status for the whole outage. Two things were already arriving and being discarded. A fractional order reports what is actually outstanding, and reports it exactly. It tracks its quantity as zero — the decimal it was submitted with lives only in the enriched record — so subtracting from that reported nothing outstanding on an order that is entirely outstanding. The quantity fields on the update were only integers because the struct declared them that way, not because the value itself is one — both dispatchers were already casting them to floating point on the way to the callback — so the fix carries the real fraction through end to end rather than rounding it into a field that never needed to be a whole number: half a share outstanding now reports 0.5, and a partly filled fractional order reports exactly what is left, not a number rounded up to the next whole share. A disconnect landing before the gateway has sent even the first word on a freshly submitted fractional order finds no enriched record either, so there is no quantity to give at all; the update carries ibapi's own sentinel for a value that was never told to it, rather than a guessed number standing in for one. The flip itself was silent: `mark_orders_uncertain` mutated the book and said nothing. The disconnect now publishes an order status update for each order it withdraws, carrying the filled and remaining quantities and the identifiers a caller keys on — the broker id and the bracket link, read back from the enriched cache rather than zeroed. The open-order snapshot then reasserted the old answer anyway. It unions the client's own records with the cached view, and the cached view still said the order was working, so for the whole outage a caller asking which orders were open was told the opposite of what the callback had just told them. The union exists to add orders this client has no record of, such as one placed in an earlier session; it now skips the ones whose status this client has withdrawn, and only those, so a genuinely newer cached status still merges. That withdrawn set is only as complete as this client's own local records, though, and an order recovered from an earlier session has none — this client never submitted it, so nothing had created a local entry for it in the first place. The status update meant to withdraw it found nothing to update, did nothing, and the stale cached record kept coming back, for precisely the order this same fix's union exists to admit. It now creates that entry the moment one is needed, seeded from the same enriched cache the snapshot itself reads, so an order this client is hearing about for the first time is both withdrawn correctly and, should that first word about it be something other than a withdrawal, not handed back with an empty contract either. What this deliberately does not do is decide what became of an order the recovery push never mentions. Absence does not identify an outcome. It cannot separate a cancellation from a rejection. An order carrying a decimal quantity holds zero in the integer quantity field, so a completeness test reads it as filled when nothing filled. And an execution for the order can still arrive after the push ends, which it cannot book if the order has been retired from the book to record a verdict — the fill and the position move are lost, which is worse than the state being unknown. The order stays Uncertain and stays in the book, where any real message supersedes it, and the caller is told the status is unknown rather than told something that may be wrong. Deciding those outcomes needs a positive statement from the gateway rather than an inference from silence — a status request on reconnect, or a capture establishing what the push guarantees. Refs deepentropy#251, which also carries a `PendingSubmit` ack deadline and the HMDS warm-up re-arm. No new wire traffic: this consumes what the gateway already sends.
userFRM
force-pushed
the
fix/reconnect-reconciliation
branch
from
July 31, 2026 13:15
d56cb83 to
0bf4e94
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An order working when the CCP link drops is marked
Uncertain, and that is the end of it. The engine stops believing the status and tells nobody, so the API layer goes on reporting the pre-disconnect status for the whole outage — andreq_open_orderskeeps asserting it.Quantities are carried as they are
OrderUpdate's filled and remaining quantities are floating point. Both dispatchers already cast them tof64on the way to the callback, so the integer was only an intermediate hop — and it truncated: a half-share order outstanding reported nothing outstanding, which is the opposite of true. A 2.5-share order with one filled now reports 1.5 remaining rather than a rounded stand-in.A disconnect arriving before the first execution report has no quantity to state at all, so it reports the unset sentinel this codebase already uses for that rather than inventing one.
What this changes
The flip is reported.
mark_orders_uncertainmutated the book silently. The disconnect now publishes an order status update for each order it withdraws, carrying filled and remaining quantities and the identifiers a caller keys on — broker id and bracket link, read back from the enriched cache rather than zeroed.The snapshot stops contradicting it.
collect_open_ordersunions the client's own records with the cached view, and the cached view still said the order was working, so a caller asking which orders were open was told the opposite of what the callback had just told them. The union exists to add orders this client has no record of, such as one placed in an earlier session; it now skips the ones whose status this client has withdrawn, and only those, so a genuinely newer cached status still merges.What this deliberately does not do
Deciding what became of an order absent from the recovery push is #384, not this. Absence does not identify an outcome:
So the order stays
Uncertainand stays in the book, where any real message supersedes it, and the caller is told the status is unknown rather than told something that may be wrong. Deciding those outcomes wants a positive statement from the gateway — a status request on reconnect, or a capture establishing what the push guarantees.The remaining items the original filing bundled with this are #384 (terminalizing from absence) and #385 (a
PendingSubmitack deadline); the HMDS warm-up re-arm it also mentioned is fixed in #367.Closes #251.
Tests
a_disconnect_tells_the_application_the_status_is_no_longer_known— the withdrawal reaches the caller with quantities and identifiers intact.an_order_the_reconnect_does_not_explain_still_books_its_execution— an order the push never mentions still books a fill and moves the position.a_cached_status_does_not_override_what_this_client_knows— withdrawn statuses are not re-imported; an order from an earlier session still merges, and a newer cached status still merges.Each fails by name when its production change is reverted.
No new wire traffic: this consumes what the gateway already sends.
Test plan
a_disconnect_tells_the_application_the_status_is_no_longer_knownby name.an_order_the_reconnect_does_not_explain_still_books_its_execution.a_cached_status_does_not_override_what_this_client_knows.cargo check --offlineclean on--lib,--lib --features python,--bins,--examples, and each integration target individually.tests/ib_paper_compatcompared against a clean checkout of the base commit — identical sorted diagnostic sets.cargo test --offline --lib— only the two knownconfig::expiry_testsfailures, which fail on the base commit for missing legacy tzdata (fixed separately in config: resolve the legacy timezone names IB states its times in (ibx#335) #336).