Usage
How Matching Works
Each cycle, the strategy loop callsengine.tick(market_id, mid_price). The paper exchange uses a 3-phase tick:
Identify fills
Buy orders with
price >= mid_price and sell orders with price <= mid_price are identified as matchable.Create fills
Fill objects are created at the order’s limit price (not the mid price). The fill size respects the partial_fill_ratio setting.Configuration
| Parameter | Default | Description |
|---|---|---|
paper_fee_rate | 0.001 | Fee rate per fill (0.1%), applied to both maker and taker unless overridden |
paper_maker_fee_rate | None | Maker fee rate (overrides paper_fee_rate for maker fills) |
paper_taker_fee_rate | None | Taker fee rate (overrides paper_fee_rate for taker fills) |
paper_partial_fill_ratio | 1.0 | Fraction of order filled per tick (1.0 = full fill) |
Maker/taker fees
Split fees by liquidity role. Makers add liquidity (limit orders resting below/above mid), takers remove it (market orders or limit orders that cross the spread).Fill includes an is_maker field indicating whether the order was a maker or taker:
In the paper exchange, maker/taker is determined by comparing the order price to the mid price at fill time. Market orders are always takers. For more realistic maker/taker simulation, use the BookSim exchange with L2 orderbook data via
hz.backtest().Partial fills
Setpaper_partial_fill_ratio to simulate partial fills:
Mid Price Source
Thetick() function needs a mid price to determine which orders fill. The strategy loop uses this priority:
- Feed mid price:
(feed.bid + feed.ask) / 2from the first feed with data - Feed last price:
feed.priceif bid/ask aren’t available - Quote mid: average of all quote mid prices as a fallback
In paper mode, the mid price drives order matching. If you have no feeds configured, the paper exchange uses your quote mid prices, which means your own quotes determine fills. Useful for basic testing but not realistic simulation.
Order Eviction
Terminal orders (filled, canceled, rejected) are evicted periodically to prevent memory growth. The strategy loop callsengine.evict_stale_orders(300.0) every 100 cycles, removing terminal orders older than 5 minutes.