Skip to main content

Overview

HftBacktest implements a complete tick-by-tick backtesting approach that processes every market event in chronological order. Unlike traditional backtesting frameworks that operate on OHLCV bars, HftBacktest replays the market exactly as it occurred, event by event.

Event-Driven Architecture

The backtesting engine operates on an event-driven model where all market activity is represented as discrete events:
// From hftbacktest/src/backtest/evs.rs
pub enum EventIntentKind {
    LocalData = 0,    // Feed data received locally
    LocalOrder = 1,   // Order response received locally
    ExchData = 2,     // Event occurred on exchange
    ExchOrder = 3,    // Order processed on exchange
}

Event Timeline

The backtester maintains separate event streams for each asset and coordinates them using an EventSet that tracks timestamps:
1

Event Collection

All events (feed data, order responses, executions) are collected with their timestamps
2

Timestamp Ordering

The EventSet identifies the next event with the earliest timestamp across all assets and processors
3

Event Processing

The event is processed by the appropriate processor (local or exchange)
4

State Update

Market depth, orders, positions, and balances are updated atomically

Local vs Exchange Processors

HftBacktest separates the simulation into two processors to accurately model the distributed nature of trading:

Local Processor

Represents your trading system:
  • Sees market feed data at local_ts (after feed latency)
  • Submits orders at the current simulation time
  • Receives order responses at local_ts (after round-trip latency)
  • Maintains your view of the order book

Exchange Processor

Represents the exchange:
  • Processes events at exch_ts (exchange timestamp)
  • Receives orders at exch_ts + entry_latency
  • Simulates order fills based on queue position
  • Sends responses back at exch_ts + entry_latency + response_latency
This dual-processor architecture from hftbacktest/src/backtest/mod.rs:673-863 ensures accurate latency modeling.

Time Control

You control simulation time through several methods:

elapse()

Advance time by a fixed duration
hbt.elapse(1_000_000)  # 1ms in nanoseconds

wait_next_feed()

Wait for the next market feed event
hbt.wait_next_feed(True, timeout)

wait_order_response()

Wait for a specific order response
hbt.wait_order_response(asset_no, order_id, timeout)

goto_end()

Process all remaining events
hbt.goto_end()

Tick-by-Tick Accuracy

The simulation processes every event in the feed data:
Every Level-2 order book update is applied:
# Each bid/ask update changes the order book
depth.update_bid_depth(price, qty, timestamp)
depth.update_ask_depth(price, qty, timestamp)

Performance Optimization

Despite processing every tick, HftBacktest maintains high performance:
Python strategies are compiled to machine code via Numba, achieving near-native performance:
@njit  # Just-in-time compilation
def strategy(hbt):
    # Runs at compiled speed
The core backtesting engine is written in Rust for maximum performance. Event processing in mod.rs:755-863 uses unsafe optimizations for zero-cost abstractions.
Data files are loaded in parallel while backtesting runs:
.parallel_load(true)  // Default enabled
  • HashMap for fast order lookup
  • VecDeque for FIFO queue management
  • Cache-aligned arrays for event processing

Why Tick-by-Tick Matters

For high-frequency strategies, bar-based backtesting is insufficient:
Bar-based backtesting problems:
  • Cannot simulate queue position (critical for maker strategies)
  • Missing intrabar price action and order book dynamics
  • Unrealistic fill assumptions
  • No latency modeling
  • Overstates profitability of HFT strategies
Tick-by-tick advantages:
  • Accurate queue position simulation
  • Real order book reconstruction
  • Proper latency accounting
  • Realistic fill simulation
  • Validates actual live trading results

Example: Event Processing Flow

Here’s how a typical market making strategy processes events:
@njit
def market_making_algo(hbt):
    while hbt.elapse(10_000_000) == 0:  # Every 10ms
        # 1. Clear filled/canceled orders
        hbt.clear_inactive_orders(asset_no)
        
        # 2. Calculate new quotes based on current market
        depth = hbt.depth(asset_no)
        mid = (depth.best_bid + depth.best_ask) / 2.0
        
        # 3. Manage existing orders
        orders = hbt.orders(asset_no)
        # Cancel orders if price has moved
        
        # 4. Submit new orders
        hbt.submit_buy_order(asset_no, bid_order_id, bid_price, qty, GTX, LIMIT, False)
        hbt.submit_sell_order(asset_no, ask_order_id, ask_price, qty, GTX, LIMIT, False)
        
        # 5. Wait for responses
        hbt.wait_order_response(asset_no, last_order_id, timeout)
Each iteration processes all market events that occurred in that 10ms window, maintaining complete accuracy.

Latency Modeling

How feed and order latencies are simulated

Order Book

Level-2 and Level-3 order book reconstruction

Queue Position

Order fill simulation using queue models

Multi-Asset

Backtesting across multiple assets and exchanges

Build docs developers (and LLMs) love