MatchingEngineSimulator#

In high-frequency strategies, it is common to encounter cases where a strategy that performs well in backtesting delivers subpar results in live trading. One major reason for this discrepancy is the underestimation of transaction costs. To more accurately reflect real trading costs, a matching engine simulator can be introduced during backtesting. Python Swordfish provides a matching engine simulator for high-frequency market data, enabling users to better evaluate and anticipate strategy performance in live trading, and make corresponding optimizations.

Order Matching Simulation#

The matching engine simulator operates as a plugin service to simulate order placement and canceling at specific time points. The engine processes these actions and generates corresponding trade outcomes.

The engine takes two primary inputs: the market data and the orders placed by the user. It simulates order processing delays by modifying the timestamp of each incoming order, adding a preset latency value. Execution results—including partial fills, rejections, and cancellations—are written to the trade detail table, while unfilled portions are retained for subsequent matching or cancellation.

Key features of the matching engine simulator include:

  • Configurable parameters such as fill ratio and matching delay.

  • When multiple orders of the same direction (buy/sell) exist, the engine follows the price-time priority principle.

  • In tick-by-tick mode, the engine builds the order book in real time. When a user order arrives, it is immediately matched against the order book to generate fills. Any unfilled portion will continue to match with subsequent market and user orders, still following price-time priority.

  • In snapshot mode, user orders are matched against the current order book. Depending on configuration, unfilled portions may continue to match with subsequent snapshots. Two matching modes are available:

    • Matching mode 1: Match against the latest trade price and the counterparty order book according to a configured ratio.

    • Matching mode 2: Match against a list of trades within a specified interval and the counterparty order book.

  • Supported order types include Limit Orders, Market Orders, and Cancel Orders. Different combinations of order type and market data type correspond to different matching rules, which are described in later sections.

Market Data Requirements for the Matching Engine#

When creating a matching engine simulator, you must specify the schema of the input tables (market data and user orders) and the output table (trade details).

  • For input tables, the engine requires specific field names, but the column order is not restricted. If the field names in a user-defined market data table differ from those required by the engine, you can use quote_col_map in the config to define the mapping. Similarly, if the field names in a user-defined user order table differ, you can define the mapping with user_order_col_map.

  • For output tables, the simulated matching engine does not require specific field names, but it will validate whether the field data types and structure are correct.

The following sections describe the mandatory fields or data types for input and output tables. The schema may vary slightly across different asset classes—please refer to the manual for details.

Market Snapshot Data#

In snapshot mode, the input table must include the following columns. The columns tradePrice and tradeQty are required only in matching mode 2; they are optional in matching mode 1.

Name

Type

Description

symbol

SYMBOL

Security identifier

symbolSource

SYMBOL

Securities market (e.g., XSHE, XSHG, CFFEX_BOND)

timestamp

TIMESTAMP

Timestamp

lastPrice

DOUBLE

Last price

upLimitPrice

DOUBLE

Upper limit price

downLimitPrice

DOUBLE

Lower limit price

totalBidQty

LONG

Total bid quantity within the interval

totalOfferQty

LONG

Total offer quantity within the interval

bidPrice

DOUBLE[]

List of bid prices

bidQty

LONG[]

List of bid quantities

offerPrice

DOUBLE[]

List of offer prices

offerQty

LONG[]

List of offer quantities

tradePrice

DOUBLE[]

List of trade prices within the interval

tradeQty

LONG[]

List of trade quantities within the interval

Examples:

quote_schema = {
  'symbol': "STRING",
  'symbolSource': "STRING",
  'timestamp': "TIMESTAMP",
  'lastPrice': "DOUBLE",
  'highestPrice2': "DOUBLE",
  'lowestPrice2': "DOUBLE",
  'highestPrice': "DOUBLE",
  'lowestPrice': "DOUBLE",
  'openPrice': "DOUBLE",
  'preClosePrice': "DOUBLE",
  'upLimitPrice': "DOUBLE",
  'downLimitPrice': "DOUBLE",
  'avgBidPrice': "DOUBLE",
  'avgOfferPrice': "DOUBLE",
  'totalBidQty': "LONG",
  'totalOfferQty': "LONG",
  'bidPrice': "DOUBLE[]",
  'bidQty': "LONG[]",
  'offerPrice': "DOUBLE[]",
  'offerQty': "LONG[]",
  'tradePrice': "DOUBLE[]",
  'tradeQty': "LONG[]",
  }

Tick Market Data#

The market data table must include the following fields:

Name

Type

Description

symbol

SYMBOL

Security identifier

symbolSource

SYMBOL

Securities market (e.g., XSHE, XSHG, CFFEX_BOND)

timestamp

TIMESTAMP

Timestamp

sourceType

INT

Source type: 0 = order; 1 = transaction

orderType

INT

Order/Transaction type:

  • order:

    • 1 = Market Order

    • 2 = Limit Order

    • 3 = Best Bid/Ask

  • transaction:

    • 0 = Trade

    • 1 = Cancel (only for SZSE, cancellations recorded in transaction stream)

price

DOUBLE

Order price

qty

LONG

Order quantity

buyNo

LONG

For transaction: corresponding original order;

For order: populated with buyNo

sellNo

LONG

For transaction: corresponding original order;

For order: populated with sellNo

direction

INT

Trade direction: 1 = Buy; 2 = Sell

seqNum

LONG

Sequence number of tick-by-tick data

Examples:

quote_schema = {
  'symbol': "STRING",
  'symbolSource': "STRING",
  'timestamp': "TIMESTAMP",
  'sourceType': "INT",
  'orderType': "INT",
  'price': "DOUBLE",
  'qty': "LONG",
  'buyNo': "LONG",
  'sellNo': "LONG",
  'direction': "INT",
  'seqNum': "LONG",
}

Stock/Futures & Options Snapshot Quotes#

  • For stock snapshots, when matching_mode=2 is specified, the market data table must include the fields tradePrice (trade price) and tradeQty (trade quantity). When matching_mode=1 is specified, the fields tradePrice and tradeQty are optional.

  • For futures and options snapshots, the table is required to include the fields highPrice (highest price) and lowPrice (lowest price).

Name

Type

Description

symbol

SYMBOL

Stock ID

symbolSource

SYMBOL

Exchange or market source

timestamp

TIMESTAMP

Timestamp

lastPrice

DOUBLE

Last traded price

upLimitPrice

DOUBLE

Upper limit price

downLimitPrice

DOUBLE

Lower limit price

totalBidQty

LONG

Total buy quantity in the interval

totalOfferQty

LONG

Total sell quantity in the interval

bidPrice

DOUBLE[]

List of buy prices

bidQty

LONG[]

List of buy quantities

offerPrice

DOUBLE[]

List of sell prices

offerQty

LONG[]

List of sell quantities

tradePrice

DOUBLE[]

List of trade prices (required when matching_mode=2)

tradeQty

LONG[]

List of trade quantities (required when matching_mode=2)

highPrice

DOUBLE

Highest price (required for futures and options)

lowPrice

DOUBLE

Lowest price (required for futures and options)

Interbank Spot Bond Snapshot#

Name

Type

Description

symbol

SYMBOL

Instrument code

symbolSource

SYMBOL

Market: Interbank

  • CFETS: X_BOND

  • SSE: CFFEX_BOND

timestamp

TIMESTAMP

Timestamp

bidSettlType

INT[]

Buy settlement type

bidMDEntrySize

LONG[]

Buy order quantity (CNY)

bidMDEntryPx

DOUBLE[]

Buy order price (clean price, CNY)

bidYield

DOUBLE[]

Buy yield to maturity

bidParty

columnar tuple

Buy quoting party (SSE bonds only)

askSettlType

INT[]

Sell settlement type

askMDEntrySize

LONG[]

Sell order quantity (CNY)

askMDEntryPx

DOUBLE[]

Sell order price (clean price, CNY)

askYield

DOUBLE[]

Sell yield to maturity

askParty

columnar tuple

Sell quoting party (SSE bonds only)

tradePrice

DOUBLE[]

List of trade prices in the interval

tradeQty

LONG[]

List of trade quantities in the interval

settlType

INT[]

Settlement type

yield

DOUBLE[]

Yield to maturity

Crypto Snapshot Market Data#

Name

Type

Description

symbol

SYMBOL

Instrument code

symbolSource

SYMBOL

Exchange

timestamp

TIMESTAMP

Timestamp

lastPrice

DECIMAL128

Last traded price

upLimitPrice

DECIMAL128

Upper limit price

downLimitPrice

DECIMAL128

Lower limit price

totalBidQty

DECIMAL128

Total buy quantity in the interval

totalOfferQty

DECIMAL128

Total sell quantity in the interval

bidPrice

DECIMAL128[]

List of buy prices

bidQty

DECIMAL128[]

List of buy quantities

offerPrice

DECIMAL128[]

List of sell prices

offerQty

DECIMAL128[]

List of sell quantities

highPrice

DECIMAL128

Highest price

lowPrice

DECIMAL128

Lowest price

tradePrice

DECIMAL128[]

List of trade prices (matching mode 2)

tradeQty

DECIMAL128[]

List of trade quantities (matching mode 2)

Minute-level/Daily Market Data#

  • For cryptocurrency minute-level and daily market data, the symbolSource field is required. For futures and stock minute-level and daily data, the symbolSource field is optional.

  • For cryptocurrency data, both price and volume fields use the DECIMAL128 data type.

Field

Type

Description

symbol

SYMBOL

Instrument code

symbolSource

SYMBOL

Market (required for minute-level and daily cryptocurrency data)

tradeTime

TIMESTAMP

Timestamp

open

DOUBLE

Opening price

low

DOUBLE

Lowest price

high

DOUBLE

Highest price

close

DOUBLE

Closing price

volume

LONG

Trading volume

amount

DOUBLE

Trading value (turnover)

upLimitPrice

DOUBLE

Upper limit price

downLimitPrice

DOUBLE

Lower limit price

User Order Schema for the Matching Engine#

Similar to market schema, user order schema depends on instrument type:

  • Stock user orders

  • Futures/options user orders

  • Crypto user orders

  • Interbank bond user orders

Stock#

Name

Type

Description

symbol

STRING

Stock symbol. Invalid for canceled orders; use orderId instead.

symbolSource

SYMBOL

Exchange

timestamp

TIMESTAMP

Timestamp

orderType

INT

Order type:

  • SSE

    • 0 = Market order, best 5 levels, remaining canceled

    • 1 = Market order, best 5 levels, remaining to limit

    • 2 = Market order, best own price

    • 3 = Market order, best opposite price

    • 5 = Limit order

    • 6 = Cancel

    • 7-10 = Protected limit variants

  • SZSE

    • 0 = Market order, best 5 levels, remaining canceled

    • 1 = Market order, immediate remaining canceled

    • 2 = Market order, best own price

    • 3 = Market order, best opposite price

    • 4 = Market order, fully executed or canceled

    • 5 = Limit order

    • 6 = Cancel

  • Minute/Daily

    • 0 = Market order

    • 5 = Limit order

    • 6 = Cancel

price

DOUBLE

Order price

orderQty

LONG

Order quantity

direction

INT

1 = Buy, 2 = Sell

orderId

LONG

User order ID, only relevant for cancellations

Futures & Options Snapshot#

Name

Type

Description

symbol

SYMBOL

Futures contract symbol

symbolSource

SYMBOL

Exchange code

timestamp

TIMESTAMP

Timestamp

orderType

INT

Order type (default 5 = Limit order):

  • 5 = Limit order

  • 6 = Cancel

  • 0 = Market order (at upper/lower limit price, time-priority)

  • 1 = Market Stop-Loss order: converts to market order once trigger price reached; can be opening or closing

  • 2 = Market Take-Profit order: converts to market order once trigger price reached; can be opening or closing

  • 3 = Limit Stop-Loss order: becomes limit order at extreme of specified price range once triggered; can be opening or closing

  • 4 = Limit Take-Profit order: becomes limit order at extreme of specified price range once triggered; can be opening or closing

price

DOUBLE

Order price

stopPrice

DOUBLE

Stop-loss or take-profit price

orderQty

LONG

Order quantity

direction

INT

Buy/sell direction:

  • 1 = Buy Open

  • 2 = Sell Open

  • 3 = Sell Close

  • 4 = Buy Close

timeInForce

INT

Order validity:

  • 0 = Good for Day (default)

  • 1 = Fill or Kill (FOK)

  • 2 = Immediate or Cancel remaining (FAK)

orderId

LONG

User order ID, only relevant for cancellations

Crypto User Orders Table#

Name

Type

Description

symbol

SYMBOL

Instrument code

symbolSource

SYMBOL

Exchange code

timestamp

TIMESTAMP

Timestamp

orderType

INT

Minute/Daily frequency mode:

  • 0 = Market order

  • 5 = Limit order

  • 6 = Cancel

price

DECIMAL128

Order price

stopPrice

DECIMAL128

Stop-loss or take-profit price

orderQty

DECIMAL128

Order quantity

direction

INT

Buy/sell direction:

  • 1 = Buy Open

  • 2 = Sell Open

  • 3 = Sell Close

  • 4 = Buy Close

orderId

LONG

User order ID, only relevant for cancellations

Interbank Spot Bond#

Name

Type

Description

symbol

SYMBOL

Instrument

time

TIMESTAMP

Timestamp

orderType

INT

  • 1 = Limit order: order price set by investor

  • 2 = Two-sided quote: provides both bid and ask prices

  • 3 = Market to Cancel: converts a market order to cancel

  • 4 = Market to Limit: converts a market order to limit

  • 5 = Flexible: investor can adjust order parameters

  • 6 = Cancel: actively cancel submitted order

  • 7 = FAK: immediately match at specified price, unmatched portion canceled

  • 8 = FOK: immediately fully match at specified price, if cannot fully match then cancel

settlType

INT

Settlement type

bidYield

DOUBLE

Bid order yield to maturity

bidPrice

DOUBLE

Bid order price

bidQty

LONG

Bid order quantity

askYield

DOUBLE

Ask order yield to maturity

askPrice

DOUBLE

Ask order price

askQty

LONG

Ask order quantity

direction

INT

  • 1 = Buy

  • 2 = Sell

  • 3 = Two-sided quote

orderID

LONG

User order ID, only relevant for cancellations

channel

STRING

Matching channel: currently supports X-BOND or ESP

Output Table Schema for the Matching Engine#

The trade detail output table tradeOutputTable stores the execution results of orders written by the engine. The table can be defined following the order below (the table name can be modified, but each field has a specific meaning, so the order of field types must not be changed). Note that when the configuration output_queue_position is set to 1, the following five fields are enabled: openVolumeWithBetterPrice, openVolumeWithWorsePrice, openVolumeAtOrderPrice, priorOpenVolumeAtOrderPrice, and depthWithBetterPrice; the last three fields are only enabled if output_time_info is set to 1.

Name

Type

Description

orderId

LONG

User order ID for the executed order

symbol

STRING

Security ID

direction

INT

1 (Buy), 2 (Sell)

sendTime

TIMESTAMP

Order send time

orderPrice

DOUBLE

Order price

orderQty

LONG

Order quantity

tradeTime

TIMESTAMP

Trade time

tradePrice

DOUBLE

Trade price

tradeQty

LONG

Trade quantity

orderStatus

INT

User order completion status:

  • 4: Submitted

  • -2: Cancel rejected

  • -1: Order rejected

  • 0: Partially filled

  • 1: Fully filled

  • 2: Cancelled

sysReceiveTime

NANOTIMESTAMP

System time when order is received

openVolumeWithBetterPrice

LONG

Total open volume at prices better than the order price

openVolumeWithWorsePrice

LONG

Total open volume at prices worse than the order price

openVolumeAtOrderPrice

LONG

Total open volume at the same price as the order

priorOpenVolumeAtOrderPrice

LONG

Total open volume at the same price as the order and earlier than this order

depthWithBetterPrice

INT

Depth levels of open orders better than the order price

receiveTime

TIMESTAMP

Latest market time when order is received

startMatchTime

NANOTIMESTAMP

Match start time

endMatchTime

NANOTIMESTAMP

Match end time

Examples:

order_detail_output = sf.table(types={
    'orderId': "LONG",
    'symbol': "STRING",
    'direction': "INT",
    'sendTime': "TIMESTAMP",
    'orderPrice': "DOUBLE",
    'orderQty': "LONG",
    'tradeTime': "TIMESTAMP",
    'tradePrice': "DOUBLE",
    'tradeQty': "LONG",
    'orderStatus': "INT",
    'sysReceiveTime': "NANOTIMESTAMP",
})

Configuration Class#

The MatchingEngineSimulatorConfig class defines simulator behavior and is passed via the config parameter. You can set configuration in three ways:

  • Dictionary

config = MatchingEngineSimulatorConfig({
    "orderbook_matching_ratio": 0.12,
    "matching_mode": 2
})
  • Attribute assignment

config = MatchingEngineSimulatorConfig()
config.latency = 0
  • Key-value access

config = MatchingEngineSimulatorConfig()
config["latency"] = 0

Configuration Options#

Option

Description

quote_col_map

Maps user quote table column names to internal field names. Only needed if column names differ from system expectations.

user_order_col_map

Maps user order table columns to internal engine fields. Keys are internal names, values are user field names. Only needed if names differ.

depth

Order book depth (5-50)

output_interval

Minimum interval for order book output, in milliseconds

latency

Simulated delay (ms) to mimic order processing:

  • -1: match immediately (stock tick, stock snapshot, bond snapshot)

  • >0: match when latest quote time > order time + latency

  • 0: match when latest quote time ≥ order time

orderbook_matching_ratio

Percentage of trades matched against order book; must be ≥ 0

matching_mode

Matching mode. Snapshot/minute/daily modes handled separately:

  • Snapshot (stocks): 1 = no interval trades; 2 = with interval trades

  • Minute (stocks, futures, crypto): 1 = limit: buyers at lowest, sellers at highest; 2 = first at close, remainder as above; market: always at close

  • Daily (stocks, futures, crypto): 1 = match at close; 2 = match at open

matching_ratio

Interval trade ratio in snapshot mode. Defaults to orderbook_matching_ratio

order_details_and_snapshot_output

Output composite table including order details and snapshots

snapshot_output

Output order book snapshots

output_time_info

Include quote receive time, match start time, and match end time. Default = False

output_reject_details

Include rejection reasons for order/cancel rejections. Default = False

output_queue_position

Queue position output mode:

  • 0 = none (default)

  • 1 = include latest quote in calculation

  • 2 = exclude latest quote in calculation

Adds 5 metrics: unfilled volumes better/worse/equal to order price, earlier equal-price volume, number of better levels

output_order_confirmation

Output order confirmations. Default = True

output_order_trade_flag

If True, indicates whether order is passive (maker) or aggressive (taker). Default = False

cpu_id

CPU core ID to bind engine thread (first received quote/order)

user_defined_order_id

If True, user-specified orderId used as external ID. Adds userOrderId column. Default = False

order_by_price

Interbank bond mode: True = match by price; False = match by yield-to-maturity

immediate_order_confirmation

If True, return order confirmation immediately (interbank bond snapshot only). Default = False

immediate_cancel

If True, cancel immediately (interbank bond snapshot only). Default = False

trade_in_lots

Interbank bond snapshot:

  • True (default): min order 100k, trade size multiple of 50k

  • False: min order 10m, trade size multiple of 10m

outputSeqNum

If True, adds seqNum column to order detail table (auto-increment 1…N)

Matching Rules#

The matching engine simulator adheres to exchange-compliant matching rules while offering flexible configuration options to accurately model order execution processes. It enables users to create realistic simulations of order fulfillment, considering factors such as latency, order book, percentage of order filled, etc.

The following sections describe the matching rules for limit orders and market orders.

Limit Order Matching Rules#

Limit orders are matched according to the specific rules based on the type of market data.

Snapshot Market Data#

When the market data is provided as snapshots, the matching process proceeds as follows:

  • Limit Order Matching for Level N:

    Buy Orders: When the order price ≥ Level 1 ask price, the order is matched sequentially against the levels of the ask side.The trade price is taken from the corresponding ask level, and the trade quantity is the minimum of: (ask level quantity * level fill ratio) and (order quantity - already filled quantity). If the order is not fully filled, it enters the pending order queue. At this price level, the order is placed at the front (i.e., preceding quantity is 0), and then enters the pending order matching phase.

  • Sell Orders: When the order price ≤ Level 1 bid price, the order is matched sequentially against the levels of the bid side. The trade price is taken from the corresponding bid level, and the trade quantity is the minimum of: (bid level quantity * level fill ratio) and (order quantity - already filled quantity). If the order is not fully filled, it enters the pending order queue. At this price level, the order is placed at the front (i.e., preceding quantity is 0), and then enters the pending order matching phase.

  • Pending Orders:

If an order’s price matches the levels on the same side of the order book, it enters the pending queue. The quantity ahead of this order equals the total quantity at the same price level. The order then proceeds to the pending matching phase.

  • Pending Order Matching (Unfilled or Partially Filled Orders):

    • Matching Mode 1:

      • If the matching ratio is set to 0, he order is matched against the opposite side in the order book following the same procedure as in step 1. The trade price equals the order price. Matching continues until the order is fully executed or the market closes. Remaining quantities can be canceled on request.

      • If the matching ratio is greater than 0, the order is matched based on the latest price and best N levels of the order book:

        • If the latest price equals the order price, the system first deducts the quantity ahead of the order by (interval traded volume - preceding quantity), which is marked as the current volume. Once preceding quantity reaches zero, the order begins execution. Trade price equals the order price; trade quantity is the smaller of the user order quantity and current volume * matching ratio.

        • If the latest price is less favorable than the order price, trade price equals the order price; trade quantity is the smaller of the user order quantity and interval actual volume * matching ratio.

        • The remaining unfilled portion of the order is matched against the corresponding levels on the opposite side of the order book at the order’s specified price.

        • The matching continues until the order is fully executed or the market closes for the day. Any unfilled or partially filled orders may be canceled according to a cancellation request.

    • Matching Mode 2: Matching is performed based on the interval trade list and the latest top levels of the snapshot order book, following the price-time priority principle:

      • The user’s buy order is matched against trades in the interval whose prices are less than or equal to the order price. First, the quantity ahead of the user’s order at the same price level (including both resting orders and earlier trades) is subtracted, defining the available executable volume. When the available volume is greater than zero, the user’s order can be executed at the order price. The executed quantity is the minimum of the remaining order quantity and the available volume.

      • The user’s sell order is matched against trades in the interval whose prices are greater than or equal to the order price. The quantity ahead of the user’s order at the same price level is deducted as the available executable volume. If the available volume is positive, execution occurs at the order price. The executed quantity is the minimum of the remaining order quantity and the available volume.

        • Any unfilled portion is then matched against the opposite side of the order book at the user’s order price. The executed quantity is the minimum of the remaining order quantity and the product of the matching quantity and the fill ratio.

        • If the order is still not fully filled, the remaining quantity ahead of the user’s order at the same price level is updated.

        • Matching continues until the order is fully executed or the market closes. Any unfilled or partially filled orders may be canceled according to a cancellation request.

Tick Market Data#

When the market data is tick-by-tick, the matching process proceeds as follows:

  • Immediate matching: Limit orders are immediately matched against the level N of the order book. Execution can follow a configurable matching ratio.

  • Unfilled or partially filled orders: Remaining quantities are matched according to the price-time priority principle:

    • For subsequent tick data, user orders are matched against incoming orders at the opposite side of the book following price-time priority.

    • Unfilled or partially filled orders can be canceled according to cancellation requests.

    • Matching continues until the order is fully executed or market close.

Market Order Matching Rules#

Market orders are executed immediately against the level N of the order book, following the specific trading rules of the Shanghai Stock Exchange (SSE) or Shenzhen Stock Exchange (SZSE).

SSE Matching Rules#

The SSE market order types are defined as follows:

  • Five Best Orders Immediate or Cancel (IOC): an order that is executed in sequence against the current five best prices on the opposite side, with the unfilled portion, if any, cancelled automatically.

  • Five Best Orders Immediate to Limit: an order that is executed in sequence against the current five best prices on the opposite side, with the unfilled portion, if any, converted to a limit order whose limit price is set to the last execution price on the same side. If the Five Best Orders Immediate to Limit order cannot be filled at all, it is either converted to a limit order whose limit price is set to the best quotation on the same side, or, in the absence of such a quotation, cancelled.

  • Same-Side Best Price Order: an order whose quotation price will be the best price on the same side in the central order book when such order enters the SSE trading system. If there is no quotation on the same side in the central order book when the Same-Side Best Price Order enters the SSE trading system, the order will be automatically cancelled.

  • Opposite-Side Best Price Order: an order whose quotation price will be the best price on the opposite side in the central order book when such order enters the SSE trading system. If there is no quotation on the opposite side in the central order book when the Opposite-Side Best Price Order enters the SSE trading system, the order will be automatically cancelled.

SZSE Matching Rules#

The SZSE market order types are defined as follows:

  • Fill or Kill (FOK): an order that must be executed in its entirety against all the orders on the opposite side in the central order book at the time the order is routed into the Exchange trading system, otherwise the entire order shall be cancelled automatically.

  • Five Best Orders Immediate or Cancel (IOC): executed in sequence against the current five best prices on the opposite side. In case that part of the order cannot be executed, the unfilled part of the order shall be cancelled automatically

  • Immediate or Cancel (IOC): an order that is executed in sequence against all the orders on the opposite side in the central order book at the time the order is routed into the Exchange trading system. In case that part of the order cannot be executed, the unfilled part of the order shall be cancelled automatically

  • Same-Side Best Price: an order whose quotation price is set at the best price on the same side in the central order book at the time the order is routed into the Exchange trading system.

  • Opposite-side Best Price: an order whose quotation price is set at the best price on the opposite side in the central order book at the time the order is routed into the Exchange trading system.

Examples#

Example 1: Simulation Matching Based on Tick-by-Tick Trade Data

Using the simulation matching engine involves five core steps: matching configuration, table schema definitions (market ticks, user orders, trade output), engine creation, data ingestion and execution, and result inspection. The following describes the workflow using tick-by-tick market data:

  • Matching configuration

import swordfish as sf
from swordfish.plugins.matching_engine_simulator import MatchingEngineSimulator
from swordfish.plugins.matching_engine_simulator import MatchingEngineSimulatorConfig

# config the engine
config = MatchingEngineSimulatorConfig()
config.latency = 0 # User order latency = 0
config.orderbook_matching_ratio = 1  # Trade fill ratio when matching against the order book
  • Table schema definitions

# Define the trade detail output table
order_detail_output = sf.table(types={
    'orderId': "LONG",
    'symbol': "STRING",
    'direction': "INT",
    'sendTime': "TIMESTAMP",
    'orderPrice': "DOUBLE",
    'orderQty': "LONG",
    'tradeTime': "TIMESTAMP",
    'tradePrice': "DOUBLE",
    'tradeQty': "LONG",
    'orderStatus': "INT",
    'sysReceiveTime': "NANOTIMESTAMP",
})
# Define the market quote table
quote_schema = {
    'symbol': "STRING",
    'symbolSource': "STRING",
    'timestamp': "TIMESTAMP",
    'sourceType': "INT",
    'orderType': "INT",
    'price': "DOUBLE",
    'qty': "LONG",
    'buyNo': "LONG",
    'sellNo': "LONG",
    'direction': "INT",
    'seqNum': "LONG",
}
# Define the user order table
user_order_schema = {
    'symbol': "STRING",
    'timestamp': "TIMESTAMP",
    'orderType': "INT",
    'price': "DOUBLE",
    'orderQty': "LONG",
    'direction': "INT",
    'orderId': "LONG",
}
  • Engine creation

dataType = 0
engine = MatchingEngineSimulator.create(
    "engine_trade", "XSHE",dataType , order_detail_output, quote_schema, user_order_schema
).config(config).submit()
  • Data ingestion (Note: market data must be sorted in chronological order)

# Insert market data
symbol = "000001"
sourceType = 0   # 0 represents order; 1 represents transaction
orderType = 2  # In the order type: 1 = market order, 2 = limit order, 3 = best bid/offer order, 10 = cancellation
               # In the transaction type: 0 = trade execution, 1 = order cancellation
orderSell = 2
orderBuy = 1

engine.insert_market(sf.any_vector([
    symbol, "XSHE", sf.scalar("2021.01.08 09:14:01.100", type="TIMESTAMP"), sourceType, orderType, 7.0, 100, 1, 1, orderBuy, 1
]))
engine.insert_market(sf.any_vector([
    symbol, "XSHE", sf.scalar("2021.01.08 09:14:01.100", type="TIMESTAMP"), sourceType, orderType, 6.0, 100, 2, 2, orderBuy, 1
]))
engine.insert_market(sf.any_vector([
    symbol, "XSHE", sf.scalar("2021.01.08 09:14:01.100", type="TIMESTAMP"), sourceType, orderType, 5.0, 100, 3, 3, orderBuy, 1
]))

# Insert orders
userOrderType = 5
# One buy order
engine.insert_order(sf.any_vector([
    symbol, sf.scalar("2021.01.08 09:14:01.400", type="TIMESTAMP"), userOrderType, 6.0, 100, orderBuy, 1
]))
  • Result inspection

# The unfilled order can be viewed in the pending orders table
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol price totalQty openQty direction isMatching
# ------- ----------------------- ------ ----- -------- ------- --------- ----------
# 1       2021.01.08T09:14:01.400 000001 6     100      100     1         0

# No record in the output table since no trade occurred
print(order_detail_output)
# orderId symbol direction sendTime orderPrice orderQty tradeTime tradePrice tradeQty orderStatus sysReceiveTime
# ------- ------ --------- -------- ---------- -------- --------- ---------- -------- ----------- --------------

Since all orders in the above code are buy orders, and the user’s submitted order is also a buy order, there are no sell orders available for matching. Now we submit a sell order:

# One sell order
engine.insert_order(sf.any_vector([
    symbol, sf.scalar("2021.01.08 09:14:01.400", type="TIMESTAMP"), userOrderType, 6.0, 100, orderSell, 1
]))

Let’s check whether it has been executed. The unfilled user order can be seen in the pending orders table, indicating that it has not been matched yet.

# The unfilled order can be viewed in the pending orders table
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol price totalQty openQty direction isMatching
# ------- ----------------------- ------ ----- -------- ------- --------- ----------
# 1       2021.01.08T09:14:01.400 000001 6     100      100     1         0
# 2       2021.01.08T09:14:01.400 000001 6     100      100     2         0

The order has not been executed because the latency is set to 0. A user order is eligible for matching only when the latest market timestamp satisfies: latest market time ≥ order time + latency. In the current scenario, the latest market tick is 2021-01-08T09:14:01.100, whereas the order time is 2021-01-08T09:14:01.400. Consequently, no market tick has triggered the execution of this order. Now, we insert a new market tick and observe again:

# Insert a market tick at 2021-01-08T09:14:01.500
engine.insert_market(sf.any_vector([
    symbol, "XSHE", sf.scalar("2021.01.08 09:14:01.500", type="TIMESTAMP"), sourceType, orderType, 7.0, 100, 1, 1, orderBuy, 1
]))
# The pending orders table is now empty
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol price totalQty openQty direction isMatching
# ------- ----------------------- ------ ----- -------- ------- --------- ----------

Now the trade output table shows the matched trades for both limit orders.

# The trade output table contains the matched trade record
print(order_detail_output)
# orderId symbol direction sendTime                orderPrice orderQty tradeTime               tradePrice tradeQty orderStatus sysReceiveTime
# ------- ------ --------- ----------------------- ---------- -------- ----------------------- ---------- -------- ----------- -----------------------------
# 1       000001 1         2021.01.08T09:14:01.400 6          100      2021.01.08T09:14:01.500 0          0        4           2025.07.22T11:54:17.289997497
# 1       000001 1         2021.01.08T09:14:01.400 6          100      2021.01.08T09:14:01.500 0          0        -1          2025.07.22T11:54:17.289997497
# 2       000001 2         2021.01.08T09:14:01.400 6          100      2021.01.08T09:14:01.500 0          0        4           2025.07.22T11:54:23.562496049
# 2       000001 2         2021.01.08T09:14:01.400 6          100      2021.01.08T09:14:01.500 0          0        -1          2025.07.22T11:54:23.562496049

Example 2: Simulation Matching Based on Level 2 Snapshot Data

According to the fields contained in the snapshot data, the matching engine simulator provides two distinct matching modes:

  • Matching Mode 1: User orders are matched against the latest price in the snapshot and the latest order book.

  • Matching Mode 2: When snapshot data is combined with tick-level trade details within a time interval, orders are matched against the list of trade prices in the interval and the latest order book.

Using the snapshot of the order book at a specific time (2022-04-14T09:35:00.040) as reference (Table 1), the latest traded price at this moment is 16.34. At this point, the user submits a sell order with a price of 16.32 and a quantity of 50,000. The matching of this order is demonstrated separately under Matching Mode 1 and Matching Mode 2. The order book at time t is shown in Table 2. The depth and construction of Market Table 1 and Market Table 2 are as follows:

Table 1:

bidQty

bidPrice

askPrice

askQty

10100

16.33

16.34

5400

22000

16.32

16.35

197300

18300

16.31

16.36

246400

113200

16.30

16.37

183400

3900

16.29

16.38

313800

12800

16.28

16.39

454600

16600

16.27

16.40

696100

17800

16.26

16.41

49000

39054

16.25

16.42

59400

4400

16.24

16.43

76300

The sample data for Table 1 is as follows:

import swordfish as sf

t1 = sf.table({
    'SecurityID': ["000001.SZ"],
    'symbolSource': ["XSHE"],
    'DateTime': [sf.scalar("2022.04.15T09:55:15.000", type="TIMESTAMP")],
    'LastPx': [16.34],
    'up_limit_px': [17.64],
    'down_limit_px': [14.44],
    'TotalBidQty':[6683254],
    'TotalOfferQty': [14644870],
    'BidPrice': sf.array_vector([[16.33, 16.32, 16.31, 16.30, 16.29, 16.28, 16.27, 16.26, 16.25, 16.24]], type="DOUBLE"),
    'BidOrderQty': sf.array_vector([[10100, 22000, 18300, 113200, 3900, 12800, 16600, 17800, 39054, 4400]], type="LONG"),
    'OfferPrice': sf.array_vector([[16.34, 16.35, 16.36, 16.37, 16.38, 16.39, 16.40, 16.41, 16.42, 16.43]], type="DOUBLE"),
    'OfferOrderQty': sf.array_vector([[5400, 197300, 246400, 183400, 313800, 454600, 696100, 49000, 59400, 76300]], type="LONG"),
    'tradePrice': sf.array_vector([[16.34]], type="DOUBLE"),
    'tradeQty': sf.array_vector([[50000]], type="LONG"),
})

Table 2:

bidQty

bidPrice

askPrice

askQty

28900

16.33

16.34

1700

22000

16.32

16.35

224800

18300

16.31

16.36

241100

113200

16.30

16.37

183500

3900

16.29

16.38

313800

12800

16.28

16.39

454600

16600

16.27

16.40

696000

17800

16.26

16.41

49000

39054

16.25

16.42

59400

4400

16.24

16.43

76300

The sample data for Table 2 is as follows:

t2 = sf.table({
    'SecurityID': ["000001.SZ"],
    'symbolSource': ["XSHE"],
    'DateTime': [sf.scalar("2022.04.15T09:55:18.000", type="TIMESTAMP")],
    'LastPx': [16.34],
    'up_limit_px': [17.64],
    'down_limit_px': [14.44],
    'TotalBidQty':[25500],
    'TotalOfferQty': [25500],
    'BidPrice': sf.array_vector([[16.33,16.32,16.31,16.30,16.29,16.28,16.27,16.26,16.25,16.24]], type="DOUBLE"),
    'BidOrderQty': sf.array_vector([[28900,22000,18300,113200,3900,12800,16600,17800,39054,4400]], type="LONG"),
    'OfferPrice': sf.array_vector([[16.34,16.35,16.36,16.37,16.38,16.39,16.40,16.41,16.42,16.43]], type="DOUBLE"),
    'OfferOrderQty': sf.array_vector([[1700,224800,241100,183500,313800,454600,696000,49000,59400,76300]], type="LONG"),
    'tradePrice': sf.array_vector([[16.34,16.33,16.34]], type="DOUBLE"),
    'tradeQty': sf.array_vector([[300,1000,24200]], type="LONG"),
})

Matching Orders Using Mode 1

In Matching Mode 1, limit orders are immediately matched against the top N levels of the order book. Any unfilled portion of the order will subsequently be matched first against the latest trade price within the interval, and then sequentially against the latest order book at the current time. In this scenario, the parameter matchingMode is set to 1, the interval fill ratio (matchingRatio) is 10%, and other specific configurations are as follows:

import swordfish as sf
from swordfish.plugins.matching_engine_simulator import MatchingEngineSimulator
from swordfish.plugins.matching_engine_simulator import MatchingEngineSimulatorConfig

# Configure the simulation matching engine
config = MatchingEngineSimulatorConfig()
config.latency = 0 # User order latency set to 0
config.orderbook_matching_ratio = 1  # Execution ratio when matching against the order book
config.depth = 10   # Matching depth of the order book, range 5 - 50
config.output_interval = 1 # Execution ratio with the order book
config.matching_mode = 1   # Matching mode for snapshot data, can be set to 1 or 2
config.matching_ratio = 0.1   # Interval execution ratio in snapshot mode; by default, equal to orderBookMatchingRatio

# Define the trade detail output table
order_detail_output = sf.table(types={
    'orderId': "LONG",
    'symbol': "STRING",
    'direction': "INT",
    'sendTime': "TIMESTAMP",
    'orderPrice': "DOUBLE",
    'orderQty': "LONG",
    'tradeTime': "TIMESTAMP",
    'tradePrice': "DOUBLE",
    'tradeQty': "LONG",
    'orderStatus': "INT",
    'sysReceiveTime': "NANOTIMESTAMP",
})

# Define the market quote table
quote_schema = sf.table( types={
    'symbol': "STRING",
    'symbolSource': "STRING",
    'timestamp': "TIMESTAMP",
    'lastPrice': "DOUBLE",
    'upLimitPrice': "DOUBLE",
    'downLimitPrice': "DOUBLE",
    'totalBidQty': "LONG",
    'totalOfferQty': "LONG",
    'bidPrice': "DOUBLE[]",
    'bidQty': "LONG[]",
    'offerPrice': "DOUBLE[]",
    'offerQty': "LONG[]"
}
)

# Define the user order table
user_order_schema = {
    'symbol': "STRING",
    'timestamp': "TIMESTAMP",
    'orderType': "INT",
    'price': "DOUBLE",
    'orderQty': "LONG",
    'direction': "INT",
    'orderId': "LONG",
}

# Create the engine
dataType = 1  # Snapshot mode
engine = MatchingEngineSimulator.create(
    "engine_snapshot1", "XSHE",dataType , order_detail_output, quote_schema, user_order_schema
).config(config).submit()

At time t, the price of the sell order is 16.32 and the quantity is 50000 shares. The order is immediately matched against the counterparty orders at price levels 16.33 and 16.32, with executed volumes of 10100 and 22000, respectively. The remaining unfilled quantity is 50000 - 10100 - 22000 = 17900, positioned at the first level on the sell side of the order book.

# Insert Market Table 1
engine.insert_market(t1)
# User submits a sell order with a price of 16.32 and quantity of 50,000 shares
engine.insert_order(sf.any_vector([
    "000001.SZ", sf.scalar("2022.04.15T09:55:15.000", type="TIMESTAMP"), sf.scalar(5,type="INT"), 16.32, 50000, sf.scalar(2,type="INT"), 1
]))

# The remaining unfilled quantity can be viewed in the pending user orders
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol    price totalQty openQty direction isMatching
# ------- ----------------------- --------- ----- -------- ------- --------- ----------
# 1       2022.04.15T09:55:15.000 000001.SZ 16.32 50000    17900   2         1

The corresponding trade detail is as follows:

# The executed trades are listed in the output table
print(order_detail_output)

# orderId symbol    direction sendTime                orderPrice orderQty tradeTime               tradePrice         tradeQty orderStatus sysReceiveTime
# ------- --------- --------- ----------------------- ---------- -------- ----------------------- ------------------ -------- ----------- -----------------------------
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 0                  0        4           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.329999999999998 10100    0           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.32              22000    0           2025.07.23T10:30:12.859071804

The order book in the next market snapshot, shown in Table 2, the latest trade price is 16.34, and the interval trade volume is 2550.

# Insert Market Table 2
engine.insert_market(t2)

The remaining order is matched against the interval’s latest trade price, resulting in an executed volume of 2550. The remaining 17900 - 2550 = 15350 shares are then matched against the first level of the buy side. After this step, the order is fully executed, the pending orders table is empty, and the resulting trade details are as follows:

# The pending user orders are now empty, indicating all user orders have been fully executed
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol    price totalQty openQty direction isMatching
# ------- ----------------------- --------- ----- -------- ------- --------- ----------

# The trade output table shows the executed trade details
print(order_detail_output)
# orderId symbol    direction sendTime                orderPrice orderQty tradeTime               tradePrice         tradeQty orderStatus sysReceiveTime
# ------- --------- --------- ----------------------- ---------- -------- ----------------------- ------------------ -------- ----------- -----------------------------
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 0                  0        4           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.329999999999998 10100    0           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.32              22000    0           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:18.000 16.32              2550     0           2025.07.23T10:30:12.859071804
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:18.000 16.32              15350    1           2025.07.23T10:30:12.859071804

# Scenario complete, stop the engine
engine.drop()

Matching Orders Using Mode 2

In Matching Mode 2, limit orders are immediately matched against the top N levels of the order book. Any unfilled orders enter the pending order process, where they are first matched against trade prices within the interval, and then sequentially matched against the latest order book at the current time. In this scenario, the parameter matchingMode is set to 2, with other specific configurations as follows:

# Configure the simulation matching engine
config = MatchingEngineSimulatorConfig()
config.latency = 0 # User order latency set to 0
config.orderbook_matching_ratio = 1  # Execution ratio when matching against the order book
config.depth = 10   # Matching depth of the order book, range 5 - 50
config.output_interval = 1 # Execution ratio with the order book
config.matching_mode = 2   # Matching mode for snapshot data, can be set to 1 or 2
config.matching_ratio = 0.1   # Interval execution ratio in snapshot mode; by default, equal to orderBookMatchingRatio

# Define the trade detail output table
order_detail_output = sf.table(types={
    'orderId': "LONG",
    'symbol': "STRING",
    'direction': "INT",
    'sendTime': "TIMESTAMP",
    'orderPrice': "DOUBLE",
    'orderQty': "LONG",
    'tradeTime': "TIMESTAMP",
    'tradePrice': "DOUBLE",
    'tradeQty': "LONG",
    'orderStatus': "INT",
    'sysReceiveTime': "NANOTIMESTAMP",
})

# Define the market quote table
quote_schema = sf.table( types={
    'symbol': "STRING",
    'symbolSource': "STRING",
    'timestamp': "TIMESTAMP",
    'lastPrice': "DOUBLE",
    'upLimitPrice': "DOUBLE",
    'downLimitPrice': "DOUBLE",
    'totalBidQty': "LONG",
    'totalOfferQty': "LONG",
    'bidPrice': "DOUBLE[]",
    'bidQty': "LONG[]",
    'offerPrice': "DOUBLE[]",
    'offerQty': "LONG[]",
    'tradePrice': "DOUBLE[]",
    'tradeQty': "LONG[]"
}
)

# Define the user order table
user_order_schema = {
    'symbol': "STRING",
    'timestamp': "TIMESTAMP",
    'orderType': "INT",
    'price': "DOUBLE",
    'orderQty': "LONG",
    'direction': "INT",
    'orderId': "LONG",
}

# Create the engine
dataType = 1  # Snapshot mode
engine = MatchingEngineSimulator.create(
    "engine_snapshot2", "XSHE",dataType , order_detail_output, quote_schema, user_order_schema
).config(config).submit()

At time t, the order book is shown in Table 1. A sell order is submitted with a price of 16.32 and a quantity of 50000 shares. The order is immediately matched against counterparty orders at price levels 16.33 and 16.32, with executed volumes of 10100 and 22000, respectively. The remaining unfilled quantity is 50000 - 10100 - 22000 = 17900, positioned at the first level on the sell side.

# Insert Market Table 1
engine.insert_market(t1)
# User submits a sell order with a price of 16.32 and quantity of 50,000 shares
engine.insert_order(sf.any_vector([
    "000001.SZ", sf.scalar("2022.04.15T09:55:15.000", type="TIMESTAMP"), sf.scalar(5,type="INT"), 16.32, 50000, sf.scalar(2,type="INT"), 1
]))
# The remaining unfilled quantity can be viewed in the pending user orders
res = engine.get_open_orders()
print(res)
# orderId timestamp               symbol    price totalQty openQty direction isMatching
# ------- ----------------------- --------- ----- -------- ------- --------- ----------
# 1       2022.04.15T09:55:15.000 000001.SZ 16.32 50000    17900   2         1

The trade detail is as follows, which is identical to the results under Mode 1:

# The executed trades are listed in the output table
print(order_detail_output)
# orderId symbol    direction sendTime                orderPrice orderQty tradeTime               tradePrice         tradeQty orderStatus sysReceiveTime
# ------- --------- --------- ----------------------- ---------- -------- ----------------------- ------------------ -------- ----------- -----------------------------
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 0                  0        4           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.329999999999998 10100    0           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.32              22000    0           2025.07.23T15:19:37.194979087

The user order occupies the best position on the sell side. The order book in the next market snapshot, shown in Table 2, the latest interval trade price list is [16.34, 16.33, 16.34], with corresponding volumes [300, 1000, 24200]. The pending order is matched sequentially against the interval trade price list. After this step, the user order is fully executed. The resulting trade details are as follows, differing from those under Matching Mode 1:

# Insert Market Table 2
engine.insert_market(t2)

# All user orders have been fully executed; the pending orders table is now empty
res = engine.get_open_orders()
print(res)
# orderId timestamp symbol price totalQty openQty direction isMatching
# ------- --------- ------ ----- -------- ------- --------- ----------

# The executed trades can be viewed in the output table
print(order_detail_output)
# orderId symbol    direction sendTime                orderPrice orderQty tradeTime               tradePrice         tradeQty orderStatus sysReceiveTime
# ------- --------- --------- ----------------------- ---------- -------- ----------------------- ------------------ -------- ----------- -----------------------------
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 0                  0        4           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.329999999999998 10100    0           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:15.000 16.32              22000    0           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:18.000 16.32              300      0           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:18.000 16.32              1000     0           2025.07.23T15:19:37.194979087
# 1       000001.SZ 2         2022.04.15T09:55:15.000 16.32      50000    2022.04.15T09:55:18.000 16.32              16600    1           2025.07.23T15:19:37.194979087

# End of scenario; stop the engine
engine.drop()

Conclusion#

The matching engine simulator in PySwordfish can simulate the matching of user orders based on snapshot data or tick-level market data. The engine supports configuration of order execution ratios and latency. When multiple user orders in the same direction are matched simultaneously, the matching follows price-time priority. This engine enables realistic emulation of actual trading during high-frequency strategy backtesting, providing crucial reference for the optimization and evaluation of high-frequency trading strategies.