Found while running the ordermatch matcher through an open-source matching-engine benchmark, the Matching Engine Performance Challenge — it cross-checks engines against the byte-identical consensus of other open source engines. The built target here is ordermatch (CMakeLists.txt:30), the QuickFIX-style sample matcher in src/ordermatch/ — a price/time-priority limit order book over two std::multimaps. This is the self-contained ordermatch book that actually compiles and runs.
When an aggressive order crosses a resting order priced better than its own limit, the fill prints at the aggressor's price instead of the resting maker's price. Under price-time priority the maker — the side already in the book — sets the execution price. Here the print follows the aggressor instead, so the trade executes away from the price the book actually offered.
Mechanism. Market::match(bid, ask) picks the trade price unconditionally from the ask (src/ordermatch/Market.cpp:109):
void Market::match( Order& bid, Order& ask )
{
double price = ask.getPrice(); // <-- always the ask's price
long quantity = 0;
...
bid.execute( price, quantity );
ask.execute( price, quantity ); // both sides filled at the ask price
}
The crossing test that gates this is iBid->second.getPrice() >= iAsk->second.getPrice() (Market.cpp:73), so the best bid can sit strictly above the best ask. When that happens because a resting BUY is sitting above an incoming SELL, ask.getPrice() is the aggressor's (lower) price, and both execute() calls stamp m_lastExecutedPrice with it (Order.h:77). The trade then prints below the resting bid — at the aggressor's price rather than the maker's — so both sides report an execution price that is not the one price-time priority dictates. The mirror case — an aggressive BUY lifting a lower resting ask — happens to print correctly, but only by accident: there the ask is the resting maker, so ask.getPrice() is already the right price.
This surfaces to any consumer of the engine's output. Application::processOrder inserts each order then runs match() (Application.cpp:186-191) and reports every executed order via fillOrder → Application::updateOrder, which sends order.getLastExecutedPrice() as FIX LastPx (Application.cpp:150). So a FIX client that rests a bid and gets hit by a marketable sell receives the wrong fill price.
Repro. Rest a BUY of qty 100 @ 105, then send an aggressor SELL of qty 100 @ 100. They cross (105 >= 100), match() sets price = ask.getPrice() = 100, and both orders end up with lastExecutedPrice = 100. The correct print is the maker's 105 — the resting bid set the price first, so under price-time priority the trade should execute at 105, not at the aggressor's 100. The benchmark catches this as a per-fill price mismatch against the consensus on every scenario where an aggressive sell hits a higher standing bid.
Classification. Convention deviation, not a crash or a lost order: quantity is conserved and the same two orders match — only the printed execution price is wrong (price-time-priority violation).
Fix. Price each fill at the resting/maker order's price rather than unconditionally at the ask. The maker is whichever side was already in the book when the aggressor arrived; in this matcher's flow that's the order already resting in the multimap before the just-inserted aggressor crosses into it. Passing the resting side's price into execute() for both fills makes the print follow the maker — and on the existing (accidentally-correct) aggressive-buy path it's a no-op, since there the ask already is the maker.
Happy to share the failing workload.
Found while running the
ordermatchmatcher through an open-source matching-engine benchmark, the Matching Engine Performance Challenge — it cross-checks engines against the byte-identical consensus of other open source engines. The built target here isordermatch(CMakeLists.txt:30), the QuickFIX-style sample matcher insrc/ordermatch/— a price/time-priority limit order book over twostd::multimaps. This is the self-containedordermatchbook that actually compiles and runs.When an aggressive order crosses a resting order priced better than its own limit, the fill prints at the aggressor's price instead of the resting maker's price. Under price-time priority the maker — the side already in the book — sets the execution price. Here the print follows the aggressor instead, so the trade executes away from the price the book actually offered.
Mechanism.
Market::match(bid, ask)picks the trade price unconditionally from the ask (src/ordermatch/Market.cpp:109):The crossing test that gates this is
iBid->second.getPrice() >= iAsk->second.getPrice()(Market.cpp:73), so the best bid can sit strictly above the best ask. When that happens because a resting BUY is sitting above an incoming SELL,ask.getPrice()is the aggressor's (lower) price, and bothexecute()calls stampm_lastExecutedPricewith it (Order.h:77). The trade then prints below the resting bid — at the aggressor's price rather than the maker's — so both sides report an execution price that is not the one price-time priority dictates. The mirror case — an aggressive BUY lifting a lower resting ask — happens to print correctly, but only by accident: there the ask is the resting maker, soask.getPrice()is already the right price.This surfaces to any consumer of the engine's output.
Application::processOrderinserts each order then runsmatch()(Application.cpp:186-191) and reports every executed order viafillOrder→Application::updateOrder, which sendsorder.getLastExecutedPrice()as FIXLastPx(Application.cpp:150). So a FIX client that rests a bid and gets hit by a marketable sell receives the wrong fill price.Repro. Rest a BUY of qty 100 @ 105, then send an aggressor SELL of qty 100 @ 100. They cross (105 >= 100),
match()setsprice = ask.getPrice() = 100, and both orders end up withlastExecutedPrice = 100. The correct print is the maker's 105 — the resting bid set the price first, so under price-time priority the trade should execute at 105, not at the aggressor's 100. The benchmark catches this as a per-fill price mismatch against the consensus on every scenario where an aggressive sell hits a higher standing bid.Classification. Convention deviation, not a crash or a lost order: quantity is conserved and the same two orders match — only the printed execution price is wrong (price-time-priority violation).
Fix. Price each fill at the resting/maker order's price rather than unconditionally at the ask. The maker is whichever side was already in the book when the aggressor arrived; in this matcher's flow that's the order already resting in the multimap before the just-inserted aggressor crosses into it. Passing the resting side's price into
execute()for both fills makes the print follow the maker — and on the existing (accidentally-correct) aggressive-buy path it's a no-op, since there the ask already is the maker.Happy to share the failing workload.