Skip to content

Honor taker TIF/kind in match_order: FOK/IOC/PostOnly/M2L (#65)#98

Merged
joaquinbejar merged 2 commits into
mainfrom
issue-65-taker-tif-semantics
Jun 23, 2026
Merged

Honor taker TIF/kind in match_order: FOK/IOC/PostOnly/M2L (#65)#98
joaquinbejar merged 2 commits into
mainfrom
issue-65-taker-tif-semantics

Conversation

@joaquinbejar

Copy link
Copy Markdown
Owner

Summary

match_order previously ignored the taker's time-in-force / order kind and
filled greedily. It now honors them with explicit single-level semantics. The
user approved this as a breaking change for the 0.9 window.

Changes

  • match_order gains taker_tif: TimeInForce and taker_kind: TakerKind
    ({Standard, PostOnly, MarketToLimit}), applied via a mutation-free pre-check
    before the FIFO sweep:
    • FOK — all-or-nothing: killed (zero trades, queue untouched) if depth
      < incoming; else fills fully.
    • PostOnly — rejected if it would cross (matchable depth present).
    • IOC / GTC / GTD / Day / MarketToLimit — fill available, report remainder.
  • New additive MatchOutcome {Filled, PartiallyFilled, NotFilled, Killed,
    Rejected} on MatchResult (#[serde(default)]) + outcome() / was_killed()
    / was_rejected(). Existing fields/accessors unchanged. NotFilled separates
    "empty level" from a FOK Killed / PostOnly Rejected.
  • New TakerKind type (src/execution/taker.rs).

Blocker fix (found by microstructure-critic review)

A zero-visible iceberg/reserve with hidden depth made match_against return
consumed=0 + re-queue-same → infinite loop (pre-existing in the GTC sweep;
newly reachable from the FOK dry-run this PR adds). Fixed by:

  • drawing the full hidden tranche when visible == 0 (normal visible > 0 path
    unchanged);
  • a shared no-progress set-aside guard in the real sweep and the dry-run —
    stalled makers are reinserted at their original sequence, so makers behind
    still match and price-time priority holds, with no counter drift;
  • a unified is_matchable() so PostOnly (has_matchable_depth) and FOK
    (matchable_quantity) agree on depth.

A second microstructure-critic pass verified the fix SOUND (FOK dry-run
still exactly predicts the sweep; FIFO/counters/determinism intact).

Public API impact

Breaking: match_order(qty, taker_id, tif, kind, ts, &gen). Migration default to
preserve old behavior: TimeInForce::Gtc, TakerKind::Standard. Migration note in
src/lib.rs; README.md regenerated. New exports: MatchOutcome, TakerKind.

Testing

cargo test: 406 (lib) + 1 (integration) + 9 (doctests) passed, 0 failed.
cargo clippy --all-targets --all-features -- -D warnings (incl.
missing_errors_doc), cargo fmt --all --check, cargo build --release: clean.

Checklist

  • Matching semantics correct (FOK no partial state; PostOnly reject; IOC/M2L
    fill-available); reviewed SOUND
  • Infinite-loop blocker fixed + regression-tested (no hang)
  • FIFO/price-time determinism, counter↔queue consistency, snapshot round-trip intact
  • No new deps; no production .unwrap(); hand-rolled PriceLevelError
  • src/lib.rs migration guide + README.md updated

Closes #65

…tOnly, M2L)

match_order now takes taker_tif: TimeInForce and taker_kind: TakerKind and
applies single-level semantics via a mutation-free pre-check:
- FOK: all-or-nothing — killed (zero trades, queue untouched) if depth < incoming.
- PostOnly: rejected if it would cross (matchable depth present).
- IOC / GTC / GTD / Day / MarketToLimit: fill available, report remainder.
New additive MatchOutcome {Filled, PartiallyFilled, NotFilled, Killed, Rejected}
on MatchResult (serde-default) with was_killed()/was_rejected(); existing fields
unchanged.

Also fixes a pre-existing infinite loop: a zero-visible iceberg/reserve with
hidden depth re-queued unchanged (consumed=0) forever, now newly reachable from
the FOK dry-run. Fixed by drawing the full hidden tranche when visible==0, a
shared no-progress set-aside guard (stalled makers reinserted at their original
sequence so makers behind still match and price-time priority holds), and a
unified is_matchable() so PostOnly and FOK agree on depth. Reviewed SOUND by
microstructure-critic.

BREAKING: match_order signature change (pre-0.9). Migration note in lib.rs.
@joaquinbejar joaquinbejar added enhancement New feature or request high Must be completed in current cycle price-level Engine: level/order_queue/snapshot/statistics + execution results labels Jun 23, 2026
@joaquinbejar joaquinbejar self-assigned this Jun 23, 2026
@joaquinbejar joaquinbejar requested a review from Copilot June 23, 2026 22:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the single-price-level matching engine to honor taker-side intent (time-in-force and kind) during PriceLevel::match_order, adds an explicit terminal MatchOutcome to disambiguate “no fill” vs “killed/rejected”, and includes a regression fix + tests for a previously possible infinite loop involving zero-visible iceberg/reserve orders.

Changes:

  • Breaking API change: match_order now takes (taker_tif: TimeInForce, taker_kind: TakerKind) and enforces FOK/IOC/PostOnly/M2L taker semantics via pre-checks and a dry-run depth computation.
  • Add MatchOutcome to MatchResult (serde-defaulted) and add TakerKind type + exports; update docs/README/examples/benches/tests accordingly.
  • Fix and regression-test an infinite-loop scenario caused by zero-progress makers (zero-visible iceberg/reserve), including a sweep “set-aside” guard and iceberg refresh handling.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/mod.rs Updates unit test calls to the new match_order signature.
src/price_level/tests/level.rs Expands/renames tests to cover new taker semantics and loop regressions; updates calls for new signature.
src/price_level/level.rs Implements taker TIF/kind pre-checks, FOK dry-run depth computation, and sweep no-progress guard + docs.
src/prelude.rs Exports MatchOutcome and TakerKind via prelude.
src/orders/order_type.rs Adds OrderType::is_matchable and fixes iceberg refresh behavior for zero-visible tranches.
src/lib.rs Adds migration guide and exports MatchOutcome/TakerKind.
src/execution/tests/match_result_trade.rs Adds tests to keep MatchOutcome in sync and serde-default coverage.
src/execution/taker.rs Introduces TakerKind definition and helpers.
src/execution/mod.rs Wires taker module and re-exports TakerKind + MatchOutcome.
src/execution/match_result.rs Introduces MatchOutcome, threads it through MatchResult lifecycle, and adds killed/rejected helpers.
README.md Mirrors the migration guide and updated semantics documentation.
examples/src/bin/simple.rs Updates example to pass TimeInForce and TakerKind.
examples/src/bin/integration_trade_roundtrip.rs Updates integration example to new match_order signature.
examples/src/bin/integration_special_orders.rs Updates example calls to include taker_tif/taker_kind.
examples/src/bin/integration_checked_arithmetic.rs Updates example calls to include taker_tif/taker_kind.
examples/src/bin/integration_basic_lifecycle.rs Updates example calls to include taker_tif/taker_kind.
examples/src/bin/hft_simulation.rs Updates simulation example calls to include taker_tif/taker_kind.
examples/src/bin/contention_test.rs Updates contention example calls to include taker_tif/taker_kind.
benches/price_level/special_orders.rs Updates benchmarks to new match_order signature.
benches/price_level/serialization.rs Updates benchmarks to new match_order signature.
benches/price_level/mixed_operations.rs Updates benchmarks to new match_order signature.
benches/price_level/match_orders.rs Updates benchmarks to new match_order signature.
benches/price_level/lifecycle.rs Updates benchmarks to new match_order signature.
benches/price_level/checked_arithmetic.rs Updates benchmarks to new match_order signature.
benches/concurrent/register.rs Updates concurrent benchmark calls to new match_order signature.
benches/concurrent/contention.rs Updates concurrent benchmark calls to new match_order signature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/execution/match_result.rs Outdated
Comment thread src/execution/match_result.rs Outdated
Comment thread src/price_level/level.rs Outdated
Comment thread src/orders/order_type.rs
@codecov-commenter

codecov-commenter commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.76190% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/orders/order_type.rs 58.82% 7 Missing ⚠️
src/price_level/level.rs 88.63% 5 Missing ⚠️
src/execution/match_result.rs 95.00% 2 Missing ⚠️
src/execution/taker.rs 50.00% 2 Missing ⚠️

❌ Your project status has failed because the head coverage (73.31%) is below the target coverage (75.00%). You can increase the head coverage or adjust the target coverage.

Files with missing lines Coverage Δ
src/execution/match_result.rs 79.43% <95.00%> (+3.57%) ⬆️
src/execution/taker.rs 50.00% <50.00%> (ø)
src/price_level/level.rs 92.77% <88.63%> (-0.28%) ⬇️
src/orders/order_type.rs 47.80% <58.82%> (+0.23%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…c comments

- MatchResult::new / with_capacity: a zero initial_quantity result is now
  is_complete=true / outcome=Filled at construction (vacuously complete),
  matching finalize's remaining==0 => Filled rule, instead of the inconsistent
  is_complete=false / NotFilled.
- matchable_quantity comment no longer overclaims it mirrors the sweep's pure
  sequence order; clarified it visits (timestamp, sequence) order but the
  fillable TOTAL is visit-order invariant (which is all FOK depends on).
- is_matchable rustdoc: dropped the broken intra-doc links to the private
  has_matchable_depth / matchable_quantity helpers; use plain code spans.
@joaquinbejar

Copy link
Copy Markdown
Owner Author

Thanks, all four addressed: zero-quantity MatchResult is now internally consistent at construction, the matchable_quantity comment is accurate about visit-order vs the order-invariant total, and the is_matchable doc links to the private helpers are dropped.

@joaquinbejar joaquinbejar merged commit 1e347df into main Jun 23, 2026
13 of 14 checks passed
@joaquinbejar joaquinbejar deleted the issue-65-taker-tif-semantics branch June 23, 2026 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request high Must be completed in current cycle price-level Engine: level/order_queue/snapshot/statistics + execution results

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decide and document taker TIF / PostOnly / MarketToLimit responsibility at the level

3 participants