orders: report the order's cumulative fill on order_status, not the last print (ibx#315) - #323
orders: report the order's cumulative fill on order_status, not the last print (ibx#315)#323userFRM wants to merge 2 commits into
Conversation
The fill block recorded the ExecID and then booked the fill only if the order was already in `context`. There was no other branch, so a fill for an order the session does not track was dropped in full: no `Fill`, no execution detail, no position update, and nothing in the log to say a fill had been seen at all. The ExecID had already been consumed by then, which made the loss permanent. The replay the gateway pushes after a reconnect — the thing that would have recovered it — is rejected as a duplicate. An order is missing from `context` in ordinary ways. It reached a terminal status and was removed, which is the cancel/fill race: the cancel ack is processed first and the fill arrives to find nothing. Or it was placed from another client, or in an earlier session; the recovery insert does not pick those up, since it only fires for a New/New report carrying a contract id and a quantity. What is left is a position the account holds and the engine does not, disagreeing with the gateway's own position feed, with no error raised either way. The execution report already carries what booking needs, so the fill is now placed from the report when the order is unknown. Both the contract and the side must be on it: a guessed side moves the position the wrong way, which is worse than reporting that the fill could not be placed, so a report without tag 54 is refused rather than defaulted. Registration goes through the fallible path, because a full instrument table is a condition to report and not one to abort an inbound message on. A replayed execution is excluded. On a fresh process the gateway resends prior executions carrying `97=Y` and their original ExecIDs, for orders that no session tracks — booking those would build a position out of history on top of the one the position feed already reports. Within a process the ExecID window covers the reconnect burst, so the marker is only consulted on the untracked path. The ExecID is recorded once the fill can be booked rather than before. An execution that could not be placed stays replayable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ast print (ibx#315) `order_status` was given the size and price of the print that triggered it where the callback's contract asks for the order as a whole. Argument 3, `filled`, received this print's quantity; argument 5, `avgFillPrice`, received this print's price. Argument 8 is `lastFillPrice` and received the same price, which is the one place it belonged. IB's contract is that `filled` is cumulative and `avgFillPrice` is volume-weighted across every print, with `lastFillPrice` describing the print. A 300-share order filling in three 100-share prints therefore reported `filled=100` three times, at three different prices each presented as the average. Average-cost and P&L calculations reading that field are wrong for any order that fills in more than one print, and `filled` never reaches the order quantity, so completion detection on it never fires. The gateway sends both numbers on every execution report — CumQty (14) and AvgPx (6). They were already parsed, but only into the execution record, never into the status callback. They now travel on the fill itself. Reading them from the order cache at dispatch time would have been smaller, but a second print arriving between the push and the drain would report its cumulative against the earlier fill; carrying them with the event keeps each one self-consistent. A report that omits either field falls back to the print, which is what the callback reported before. Every consumer of the drained fill is corrected, not only the one the report named. The Python compat client dispatches the same fill through its own `order_status` and had the identical pair; the open-order snapshot both clients poll stored the print as the order's filled quantity, so a partial fill would have had the push callback and `reqOpenOrders` disagreeing about the same order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4d509d0 to
109cb48
Compare
|
Four corrections in Two integration targets stopped compiling. Adding fields to A debug hook removed. An Scope trimmed to the message. The commit had also carried an unrelated server-tag redesign, byte-identical to the one in #286, under a title about filled quantity. Stripped to what the message claims. A misindented field corrected to rustfmt. The functional change is unaltered: |
…totals on the fill, booked quantity preserved
Summary
order_statuswas given the size and price of the print that triggered it, where the callback's contract asks for the order as a whole. Argument 3 (filled) received the print's quantity and argument 5 (avgFillPrice) its price. Argument 8,lastFillPrice, received the same price — the one place it belonged.filledcumulative,avgFillPricevolume-weighted across every print,lastFillPricethis print.filled=100 avg=P1 → filled=200 avg=(P1+P2)/2 → filled=300 avg=VWAP. It reportedfilled=100three times, at three different prices each presented as the average.avgFillPriceis wrong whenever an order fills in more than one print, which above a round lot is most of the time.fillednever reaches the order quantity, so completion detection on that field never fires.Closes #315.
Change
The gateway sends both numbers on every execution report — CumQty (14) and AvgPx (6). They were already parsed, but only into the execution record, never into the status callback.
They now travel on the fill itself. Reading them from the order cache at dispatch time would have been a smaller change, but a second print arriving between the push and the drain would report its cumulative against the earlier fill; carrying them with the event keeps each one self-consistent. A report that omits either field falls back to the print, which is what the callback reported before.
Every consumer, not just the one the issue named
order_statusand had the identical pair.filled=200whilereqOpenOrderssaidfilled=100for the same order.Tests
One end-to-end assertion pins the whole callback string, so it covers
filledandavgFillPricetogether:Reverting the dispatch produces
...:100:100:151, which is the reported bug exactly. Two more tests drive an execution report through the handler and assert the drained fill's cumulative pair comes off tags 14 and 6 rather than 32 and 31, and that it falls back to the print when neither is present.Test plan
cargo test --offline --lib— 814 passed. The 2 failures areconfig::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.🤖 Generated with Claude Code