Skip to content

Fix PartialFillExchange exact-qty fills and local partial-fill accounting - #323

Open
vedjaw wants to merge 1 commit into
nkaz001:masterfrom
vedjaw:fix/partial-fill-exact-qty-and-local-accounting
Open

vedjaw wants to merge 1 commit into
nkaz001:masterfrom
vedjaw:fix/partial-fill-exact-qty-and-local-accounting

Conversation

@vedjaw

@vedjaw vedjaw commented Aug 1, 2026

Copy link
Copy Markdown

Summary

Test plan

  • CI: existing backtest / PartialFillExchange tests
  • Manual: resting order partially filled under probabilistic queue models — local position/balance should move on each partial, and exact filled_qty == leaves_qty should complete without error

Fixes #312
Fixes #316

Made with Cursor

…ting

Treat filled_qty == leaves_qty as a complete fill (>=) so InvalidOrderStatus
is not raised when the queue model reports an exact fill (nkaz001#312). Apply
PartiallyFilled responses on the local side so position/balance track
incremental exec_qty (nkaz001#316).

Co-authored-by: Cursor <cursoragent@cursor.com>
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@Altairpaca

Copy link
Copy Markdown

Independent verification of this change against head 22856574, using a focused accounting suite I wrote while reproducing #316 (12 integration tests: partial→position, multi-chunk accumulation, partial→cancel, rejected cancel/modify echo paths, fee/balance/side conservation, zero-qty, a NoPartial control, and the reporter's power_prob_queue_model scenario).

The core accounting fix works. partial_fill_is_applied_to_local_state, partial_then_final_fill_reaches_full_position, multiple_partial_chunks_are_applied_exactly_once, fee_and_balance_are_applied_per_execution, cancel_after_partial_fill_does_not_apply_extra_fill, the NoPartial control and the reporter's scenario all pass with your patch.

However, the Filled || PartiallyFilled predicate alone is not sufficient. A rejected request echoes the local request clone, which can still carry the previous execution's status/exec_qty (the reject branches in local.rs rewrite only req), so applying fills on status alone replays stale execution fields. On your head, 5 of the 12 tests fail with concrete double counts (cargo test -p hftbacktest --test issue_316_partial_fill_accounting):

test expected your head
latency_rejected_cancel_does_not_replay_stale_exec_qty position 5 10
rejected_cancel_does_not_replay_stale_partial_exec_qty position 20 25
rejected_modify_does_not_replay_stale_partial_exec_qty position 20 25
rejected_cancel_after_full_fill_does_not_replay_filled_response position 20 35
zero_qty_execution_is_a_noop num_trades 0 1

The minimal additional guards that make all 12 pass:

if (order.status == Status::Filled || order.status == Status::PartiallyFilled)
    && order.req != Status::Rejected
    && order.exec_qty > 0.0
{
    self.state.apply_fill(&order);
}

Zero-quantity fills cannot be masked by the exec_qty > 0 guard: every reachable fill call in partialfillexchange.rs is already gated by a positive executable quantity.

Run comparison: baseline 5f3ec40 → 11 failed / 1 passed; your head 22856574 → 7 passed / 5 failed; with the two guards → 12 passed / 0 failed.

I did not open a competing PR. If you want the tests, they are on my fork branch Altairpaca/hftbacktest:fix/316-partial-fill-accounting (test file + these guards, on top of 5f3ec40); I am happy to trim them into a test-only follow-up PR or for you to pull them into this branch — whichever the maintainer prefers. Credit to @readerwei for the original reproduction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants