HftBacktest

Tick-by-tick backtesting that models order queue position and feed and order latency.

by nkaz001

Last updated

From
Free
Licence
MIT
Self-hosted
Yes
Platforms
Library

What it is

A backtester for strategies whose profit and loss is decided by where an order sat in the queue and by how long it took the exchange to hear about it. It replays a full tick feed — every Market-By-Price depth update and every trade — reconstructs the book from it, and simulates your order against that book with two things most engines do not have: a model of your position in the queue at your price level, and separate latency for the feed arriving, the order leaving and the response coming back.

That is the whole reason to pick it over anything else in this category. The six libraries already here decide a limit fill by comparing your price to a bar's range, which means a passive order at the touch fills every time the market trades through it. At a horizon of minutes that approximation is survivable. At the horizon a market maker works at, it is the entire result: a strategy that quotes at the best bid and collects the spread is profitable in a candle backtest by construction, because the candle backtest has no concept of the forty other orders that were at that price first.

The trade for that accuracy is paid in three places — the data, the run time, and the fact that the interesting parts of the engine are Rust. All three are below.

Data & coverage

It ships no data, and this is the largest cost of using it. The feed format is an eight-field NumPy structured array — event flags, exchange timestamp, local timestamp, price, quantity, order id, and two reserved fields — loaded from npz or npy. Two timestamps rather than one is the point: the gap between them is the feed latency the simulation uses, so data recorded without a local receive timestamp cannot drive the latency model at all.

Converters shipped in the Python package (read from the 2.4.4 wheel, not the README): binancefutures, binancehistmktdata, bybit, bybithistmktdata, hyperliquid, mexc, tardis and databento, plus snapshot and validation helpers. So the practical venue list for a crypto reader is Binance Futures, Binance historical market data, Bybit, Hyperliquid and MEXC direct, and anything Tardis or DataBento carries by way of those two. There is no CCXT anywhere in the project, in either half.

Level-3 is supported and is mostly not a crypto feature. The engine reconstructs the book from Market-By-Order feeds as well as Market-By-Price, and with MBO your queue position is known rather than modelled. But the docs' own Level-3 tutorial builds its feed from DataBento's CME Market-By-Order data for the BTCM4 contract, because crypto venues publish aggregated depth and not per-order messages. On a crypto exchange you are on the Market-By-Price path, and the docs say so directly: the queue models exist because "if an exchange doesn't provide Market-By-Order, you have to guess it by modeling".

Queue models, in order of pessimism. RiskAverseQueueModel advances your order only when a trade happens at your price and assumes every cancellation happens behind you — the most conservative assumption available. ProbQueueModel and its power and log variants assume cancellations occur on both sides of your position with a probability that depends on where you are, which is closer to how a book actually empties and is the one you have to justify.

Latency models. ConstantLatency takes an entry and a response figure in the same time unit as the data. IntpOrderLatency interpolates from real recorded latencies, and the docs tell you how to collect them: submit unexecutable orders on a schedule and record request, exchange and receipt timestamps. A third option generates artificial order latency from the feed latency in your data when you have no recordings. The first is a placeholder, the second is the reason the project exists, and the difference between them on a quoting strategy is not small.

Fees are chosen, not defaulted into. The asset builder takes one of three fee models — trading_value_fee_model, trading_qty_fee_model, flat_per_trade_fee_model — and the documented example passes 0.0002 maker and 0.0007 taker with the note that a negative maker figure is a rebate. Rebates matter here more than in any other card in this category, because a maker rebate is often the whole edge being tested.

Integrations

Two implementations of one design. The Python package runs your strategy inside a Numba njit function against a compiled Rust core; the Rust crate runs the same simulation natively and is where the live bot lives. Both are versioned together and released together — rust-v0.9.4 and py-v2.4.4 on the same day.

Live trading is Rust only, and the wheel proves it rather than the README claiming it: the compiled extension in Python 2.4.4 does not contain build_hashmap_livebot or build_roivec_livebot, so the package's own LIVE_FEATURE flag resolves to false on install. The Rust connectors are three — Binance Futures, Binance spot and Bybit — each speaking the exchange's own WebSocket and REST directly.

Results come out through a Recorder that writes timestamped mid price, position, balance, fee, trade count, volume and notional, and a stats module that turns those into a summary table and equity plots through Polars and matplotlib or HoloViews. Recorder.to_npz() is the only writer in the package — there is no CSV or Parquet export, which is why this card claims none.

The documentation carries a "Market Maker Program" page covering Binance Futures, Bybit Futures, OKX Futures and Hyperliquid, which tells you plainly who this is aimed at: people whose fee tier depends on quoting obligations they have to meet. That page opens by disclaiming its own accuracy and telling you to check with the exchange, which is the correct way to publish a list like that and also a reason not to plan against it.

Limitations

Replay cannot model your own impact. The docs state the assumption in the first paragraph of the order-fill page: your order changes nothing in the replayed market. A liquidity-taking order is filled at the best price for its whole size under the default exchange model, and under the partial-fill model it walks the recorded book while that book stays exactly as recorded. Size your simulated orders as though they are invisible, because in this engine they are.

Nine months without a commit. Master last moved on 2025-12-23 and the last release is dated 2025-12-10, against 4,728 stars, 6 open issues and 12 open pull requests as of 2026-09-19. The repository is not archived and nothing about a file-format-plus-converter design rots quickly — but if Bybit changes a WebSocket payload, the fix is yours.

The extension points are Rust, and the tutorial says so three times. Custom latency model, custom queue model, custom exchange model: "To implement your own … please use Rust." The Python surface is configuration over a fixed menu. If your reason for choosing this engine is that the provided queue models do not match your venue, you are writing Rust, and that should be a decision made before the data is downloaded rather than after.

No portfolio layer and no risk layer. It simulates orders against books. There is no cross-asset margin engine, no funding-payment model for perpetuals, no liquidation model and no position-sizing framework — multi-asset means several books replayed on one clock, not a portfolio. Balance, position and fees per asset are what the recorder gives you, and everything above that you write.

No data, and the one pointer in the README is somebody else's server. The README links a third-party host described as "hosted by the supporter" for USD-M futures data; on 2026-09-19 that host did not present a certificate matching its own name, so an ordinary HTTPS fetch of it fails. Either record your own feed with the shipped collector, or buy the ticks.

Documentation disagrees with the package about Python. The docs index still says 3.10+; the published 2.4.4 metadata requires 3.11 or newer, as does the README. Minor, and a useful reminder that the readthedocs build is not regenerated from the same source of truth as the wheel.

Alternatives

If your strategy holds for hours and turns over a few times a week, this is the wrong instrument and an expensive one: you will spend the first week on data before producing a number that a candle backtester would have produced in an afternoon and been approximately right about. The rest of Backtesting & Research Libraries is where that reader should start.

Choose this one when the question is specifically whether a passive quote gets filled — market making, rebate capture, queue-position strategies, anything where the answer changes if the order ahead of yours cancels. It is the only engine in this category that can answer that question at all, and the only one whose backtest is allowed to tell you that your edge was always latency.

Specs

Interfaces
Python, Rust
Export
None
Asset classes
Spot, Perpetuals, Futures
Chains
Venues
CEX, Derivatives
KYC required
No
Platforms
Library
AI features
None
Capabilities
Backtesting, Automation, Live trading
Pricing verified
Capabilities verified
Coverage verified

Also worth comparing

  • Backtesting.pyTwo thousand lines, one OHLC series, and no idea what a funding payment is.
  • BarterRust trading engine and market-data streams — live execution is a trait you implement.
  • LEANThe engine behind QuantConnect, Apache-2.0 and runnable on your own machine.
  • LumibotOne Python strategy that backtests and then trades live, across eight CCXT exchanges.
  • NautilusTraderEvent-driven Rust engine that settles perpetual funding at the venue boundary.
  • JessePython backtesting and research framework whose live-trading half is a paid plugin.

On these shelves

Background

How this part of the industry works, rather than which product to pick.

  • What a crypto backtest silently assumesEvery engine ships a fill rule and a cost model with defaults. What those defaults assume about fees, funding and liquidation, and which engines model which.

FAQ

How is HftBacktest different from a candle backtester?

A candle backtester decides whether your limit order filled by comparing its price to the bar's low or high. This one replays every book update and every trade, tracks where your order sits in the queue at its price level, and fills it only when the queue in front of it is gone. On a market-making strategy those two methods do not disagree by a few percent — they disagree about whether there is a strategy.

Can I run a live bot from Python?

No. The live connectors are Rust, and the published Python wheel does not even contain the live-bot constructors — the symbols build_hashmap_livebot and build_roivec_livebot are absent from the compiled extension in hftbacktest 2.4.4, so the package's LIVE_FEATURE flag is false. Python is the research half; going live means the Rust crate.

Does it come with tick data?

No, and tick data is the expensive part. It ships converters for Binance Futures, Bybit, Hyperliquid, MEXC, Tardis and DataBento feeds into its own NumPy format, plus a collector you run yourself. Budget for storage — full book updates for one perpetual run to gigabytes a day.

Is it still maintained?

It is not archived, but the last commit to master is dated 2025-12-23 and the last release, rust-v0.9.4 and py-v2.4.4, is dated 2025-12-10 — nine months of silence as of 2026-09-19. Nothing is broken by that on its own, since the file format and the exchange converters are stable, but a new venue's feed format is your problem.