# Table of Contents - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [HftBacktest — hftbacktest](#hftbacktest-hftbacktest) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Working with Market Depth and Trades — hftbacktest](#working-with-market-depth-and-trades-hftbacktest) - [High-Frequency Grid Trading — hftbacktest](#high-frequency-grid-trading-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Level-3 Backtesting — hftbacktest](#level-3-backtesting-hftbacktest) - [Unknown](#unknown) - [Impact of Order Latency — hftbacktest](#impact-of-order-latency-hftbacktest) - [Market Making with Alpha - Order Book Imbalance — hftbacktest](#market-making-with-alpha-order-book-imbalance-hftbacktest) - [High-Frequency Grid Trading - Comparison Across Other Exchanges — hftbacktest](#high-frequency-grid-trading-comparison-across-other-exchanges-hftbacktest) - [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading — hftbacktest](#gu-ant-lehalle-fernandez-tapia-market-making-model-and-grid-trading-hftbacktest) - [Market Making with Alpha - Basis — hftbacktest](#market-making-with-alpha-basis-hftbacktest) - [Market Making with Alpha - APT — hftbacktest](#market-making-with-alpha-apt-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [High-Frequency Grid Trading - Simplified from GLFT — hftbacktest](#high-frequency-grid-trading-simplified-from-glft-hftbacktest) - [Probability Queue Position Models — hftbacktest](#probability-queue-position-models-hftbacktest) - [Queue-Based Market Making in Large Tick Size Assets — hftbacktest](#queue-based-market-making-in-large-tick-size-assets-hftbacktest) - [Making Multiple Markets — hftbacktest](#making-multiple-markets-hftbacktest) - [Fusing Depth Data — hftbacktest](#fusing-depth-data-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Accelerated Backtesting — hftbacktest](#accelerated-backtesting-hftbacktest) - [Research Pricing Framework — hftbacktest](#research-pricing-framework-hftbacktest) - [Integrating Custom Data — hftbacktest](#integrating-custom-data-hftbacktest) - [Getting Started — hftbacktest](#getting-started-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [Migration to v2 — hftbacktest](#migration-to-v2-hftbacktest) - [Data — hftbacktest](#data-hftbacktest) - [Order Fill — hftbacktest](#order-fill-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [Market Maker Program — hftbacktest](#market-maker-program-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [Constants — hftbacktest](#constants-hftbacktest) - [Initialization — hftbacktest](#initialization-hftbacktest) - [Data Validation — hftbacktest](#data-validation-hftbacktest) - [Statistics — hftbacktest](#statistics-hftbacktest) - [Backtester — hftbacktest](#backtester-hftbacktest) - [Data Utilities — hftbacktest](#data-utilities-hftbacktest) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Getting Started — hftbacktest](#getting-started-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [Asset Types — hftbacktest](#asset-types-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [Order Latency Models — hftbacktest](#order-latency-models-hftbacktest) - [Queue Models — hftbacktest](#queue-models-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [Initialization — hftbacktest](#initialization-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [High-Frequency Grid Trading - Comparison Across Other Exchanges — hftbacktest](#high-frequency-grid-trading-comparison-across-other-exchanges-hftbacktest) - [Migration to v2 — hftbacktest](#migration-to-v2-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [Integrating Custom Data — hftbacktest](#integrating-custom-data-hftbacktest) - [Working with Market Depth and Trades — hftbacktest](#working-with-market-depth-and-trades-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [High-Frequency Grid Trading — hftbacktest](#high-frequency-grid-trading-hftbacktest) - [Impact of Order Latency — hftbacktest](#impact-of-order-latency-hftbacktest) - [Order Fill — hftbacktest](#order-fill-hftbacktest) - [Backtester — hftbacktest](#backtester-hftbacktest) - [Data Validation — hftbacktest](#data-validation-hftbacktest) - [Stat — hftbacktest](#stat-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Unknown](#unknown) - [Index — hftbacktest](#index-hftbacktest) - [Data — hftbacktest](#data-hftbacktest) - [Impact of Order Latency — hftbacktest](#impact-of-order-latency-hftbacktest) - [Impact of Order Latency — hftbacktest](#impact-of-order-latency-hftbacktest) - [Probability Queue Position Models — hftbacktest](#probability-queue-position-models-hftbacktest) - [Probability Queue Position Models — hftbacktest](#probability-queue-position-models-hftbacktest) - [Order Fill — hftbacktest](#order-fill-hftbacktest) - [Getting Started — hftbacktest](#getting-started-hftbacktest) - [High-Frequency Grid Trading — hftbacktest](#high-frequency-grid-trading-hftbacktest) - [Constants — hftbacktest](#constants-hftbacktest) - [High-Frequency Grid Trading — hftbacktest](#high-frequency-grid-trading-hftbacktest) - [Data — hftbacktest](#data-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [Initialization — hftbacktest](#initialization-hftbacktest) - [Data Validation — hftbacktest](#data-validation-hftbacktest) - [Working with Market Depth and Trades — hftbacktest](#working-with-market-depth-and-trades-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [Statistics — hftbacktest](#statistics-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [Migration to v2 — hftbacktest](#migration-to-v2-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Migration to v2 — hftbacktest](#migration-to-v2-hftbacktest) - [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading — hftbacktest](#gu-ant-lehalle-fernandez-tapia-market-making-model-and-grid-trading-hftbacktest) - [Order Fill — hftbacktest](#order-fill-hftbacktest) - [Constants — hftbacktest](#constants-hftbacktest) - [Data Preparation — hftbacktest](#data-preparation-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [Data Validation — hftbacktest](#data-validation-hftbacktest) - [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading — hftbacktest](#gu-ant-lehalle-fernandez-tapia-market-making-model-and-grid-trading-hftbacktest) - [Making Multiple Markets — hftbacktest](#making-multiple-markets-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Initialization — hftbacktest](#initialization-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Probability Queue Position Models — hftbacktest](#probability-queue-position-models-hftbacktest) - [Order Fill — hftbacktest](#order-fill-hftbacktest) - [Examples — hftbacktest](#examples-hftbacktest) - [Data — hftbacktest](#data-hftbacktest) - [Backtester — hftbacktest](#backtester-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [Market Making with Alpha - Order Book Imbalance — hftbacktest](#market-making-with-alpha-order-book-imbalance-hftbacktest) - [Level-3 Backtesting — hftbacktest](#level-3-backtesting-hftbacktest) - [Market Making with Alpha - Basis — hftbacktest](#market-making-with-alpha-basis-hftbacktest) - [Migration to v2 — hftbacktest](#migration-to-v2-hftbacktest) - [JIT Compilation Overhead — hftbacktest](#jit-compilation-overhead-hftbacktest) - [Data — hftbacktest](#data-hftbacktest) - [Latency Models — hftbacktest](#latency-models-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Statistics — hftbacktest](#statistics-hftbacktest) - [Probability Queue Position Models — hftbacktest](#probability-queue-position-models-hftbacktest) - [Index — hftbacktest](#index-hftbacktest) - [Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest](#risk-mitigation-through-price-protection-in-extreme-market-conditions-hftbacktest) - [Debugging Backtesting and Live Discrepancies — hftbacktest](#debugging-backtesting-and-live-discrepancies-hftbacktest) - [Making Multiple Markets - Introduction — hftbacktest](#making-multiple-markets-introduction-hftbacktest) - [Order Latency Data — hftbacktest](#order-latency-data-hftbacktest) - [Making Multiple Markets — hftbacktest](#making-multiple-markets-hftbacktest) - [Integrating Custom Data — hftbacktest](#integrating-custom-data-hftbacktest) - [Working with Market Depth and Trades — hftbacktest](#working-with-market-depth-and-trades-hftbacktest) - [Constants — hftbacktest](#constants-hftbacktest) - [Queue-Based Market Making in Large Tick Size Assets — hftbacktest](#queue-based-market-making-in-large-tick-size-assets-hftbacktest) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/latest/#hftbacktest "Link to this heading") ================================================================================================= [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.11+-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.90-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/latest/#high-frequency-trading-backtesting-tool "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------- This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/latest/#key-features "Link to this heading") --------------------------------------------------------------------------------------------------- * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on Level-2 Market-By-Price and Level-3 Market-By-Order feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/latest/#documentation "Link to this heading") ----------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Tutorials you’ll likely find interesting: * [High-Frequency Grid Trading - Simplified from GLFT](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) * [Accelerated Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) * [Pricing Framework](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html) Why Accurate Backtesting Matters — Not Just Conservative Approach[](https://hftbacktest.readthedocs.io/en/latest/#why-accurate-backtesting-matters-not-just-conservative-approach "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Trading is a highly competitive field where only the small edges usually exist, but they can still make a significant difference. Because of this, backtesting must accurately simulate real-world conditions.: It should neither rely on an overly pessimistic approach that hides these small edges and profit opportunities, nor on an overly optimistic one that overstates them through unrealistic simulation. Or at the very least, you should clearly understand what differs from live trading and by how much, since sometimes fully accurate backtesting is not practical due to the time it requires. This is not about overfitting at the start—before you even consider issues like overfitting, you need confidence that your backtesting truly reflects real-world execution. For example, if you run a live trading strategy in January 2025, the backtest for that exact period should produce results that closely align with the actual results. Once you’ve validated that your backtesting can accurately reproduce live trading results, then you can proceed to deeper research, optimization, and considerations around overfitting. Accurate backtesting is the foundation. Without it, all further analysis—whether conservative or aggressive—becomes unreliable. Getting started[](https://hftbacktest.readthedocs.io/en/latest/#getting-started "Link to this heading") --------------------------------------------------------------------------------------------------------- ### Installation[](https://hftbacktest.readthedocs.io/en/latest/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/latest/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/latest/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/latest/#tutorials "Link to this heading") --------------------------------------------------------------------------------------------- * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [High-Frequency Grid Trading - Comparison Across Other Exchanges](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html) * [High-Frequency Grid Trading - Simplified from GLFT](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) * [Level-3 Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Level-3%20Backtesting.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Market Making with Alpha - Basis](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html) * [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) * [Queue-Based Market Making in Large Tick Size Assets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html) * [Fusing Depth Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html) * [Accelerated Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) * [Pricing Framework](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html) Examples[](https://hftbacktest.readthedocs.io/en/latest/#examples "Link to this heading") ------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/latest/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/latest/#migration-to-v2 "Link to this heading") --------------------------------------------------------------------------------------------------------- Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/latest/#roadmap "Link to this heading") ----------------------------------------------------------------------------------------- Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/latest/#contributing "Link to this heading") --------------------------------------------------------------------------------------------------- Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#hftbacktest "Link to this heading") ==================================================================================================== [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.10+-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.87-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#high-frequency-trading-backtesting-tool "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#key-features "Link to this heading") ------------------------------------------------------------------------------------------------------ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#documentation "Link to this heading") -------------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Getting started[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#getting-started "Link to this heading") ------------------------------------------------------------------------------------------------------------ ### Installation[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#tutorials "Link to this heading") ------------------------------------------------------------------------------------------------ * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) * [Level-3 Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Level-3%20Backtesting.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Market Making with Alpha - Basis](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html) * [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) * [Queue-Based Market Making in Large Tick Size Assets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html) Examples[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#examples "Link to this heading") ---------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#migration-to-v2 "Link to this heading") ------------------------------------------------------------------------------------------------------------ Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#roadmap "Link to this heading") -------------------------------------------------------------------------------------------- Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/#contributing "Link to this heading") ------------------------------------------------------------------------------------------------------ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#hftbacktest "Link to this heading") ==================================================================================================== [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.10-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.80.1-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#high-frequency-trading-backtesting-tool "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#key-features "Link to this heading") ------------------------------------------------------------------------------------------------------ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#documentation "Link to this heading") -------------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Getting started[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#getting-started "Link to this heading") ------------------------------------------------------------------------------------------------------------ ### Installation[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#tutorials "Link to this heading") ------------------------------------------------------------------------------------------------ * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) Examples[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#examples "Link to this heading") ---------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#migration-to-v2 "Link to this heading") ------------------------------------------------------------------------------------------------------------ Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#roadmap "Link to this heading") -------------------------------------------------------------------------------------------- Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/#contributing "Link to this heading") ------------------------------------------------------------------------------------------------------ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#hftbacktest "Link to this heading") ==================================================================================================== [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.11+-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.89-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#high-frequency-trading-backtesting-tool "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#key-features "Link to this heading") ------------------------------------------------------------------------------------------------------ * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on Level-2 Market-By-Price and Level-3 Market-By-Order feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#documentation "Link to this heading") -------------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Tutorials you’ll likely find interesting: * [High-Frequency Grid Trading - Simplified from GLFT](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) * [Accelerated Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) Why Accurate Backtesting Matters — Not Just Conservative Approach[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#why-accurate-backtesting-matters-not-just-conservative-approach "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Trading is a highly competitive field where only the small edges usually exist, but they can still make a significant difference. Because of this, backtesting must accurately simulate real-world conditions.: It should neither rely on an overly conservative approach that hides these small edges and profit opportunities, nor on an overly aggressive one that overstates them through unrealistic simulation. Or at the very least, you should clearly understand what differs from live trading and by how much, since sometimes fully accurate backtesting is not practical due to the time it requires. This is not about overfitting at the start—before you even consider issues like overfitting, you need confidence that your backtesting truly reflects real-world execution. For example, if you run a live trading strategy in January 2025, the backtest for that exact period should produce results that closely align with the actual results. Once you’ve validated that your backtesting can accurately reproduce live trading results, then you can proceed to deeper research, optimization, and considerations around overfitting. Accurate backtesting is the foundation. Without it, all further analysis—whether conservative or aggressive—becomes unreliable. Getting started[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#getting-started "Link to this heading") ------------------------------------------------------------------------------------------------------------ ### Installation[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#tutorials "Link to this heading") ------------------------------------------------------------------------------------------------ * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [High-Frequency Grid Trading - Comparison Across Other Exchanges](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html) * [High-Frequency Grid Trading - Simplified from GLFT](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) * [Level-3 Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Level-3%20Backtesting.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Market Making with Alpha - Basis](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html) * [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) * [Queue-Based Market Making in Large Tick Size Assets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html) * [Fusing Depth Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html) * [Accelerated Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) Examples[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#examples "Link to this heading") ---------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#migration-to-v2 "Link to this heading") ------------------------------------------------------------------------------------------------------------ Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#roadmap "Link to this heading") -------------------------------------------------------------------------------------------- Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/#contributing "Link to this heading") ------------------------------------------------------------------------------------------------------ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/#) * HftBacktest * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/index.rst) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/v1.8.4/#hftbacktest "Permalink to this heading") ====================================================================================================== [![Codacy](https://app.codacy.com/project/badge/Grade/e2cef673757a45b18abfc361779feada)](https://www.codacy.com/gh/nkaz001/hftbacktest/dashboard?utm_source=github.com&utm_medium=referral&utm_content=nkaz001/hftbacktest&utm_campaign=Badge_Grade) [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/v1.8.4/#high-frequency-trading-backtesting-tool "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------- This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Rust implementation with experimental features[](https://hftbacktest.readthedocs.io/en/v1.8.4/#rust-implementation-with-experimental-features "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot using the same algo code. Please see [rust](https://github.com/nkaz001/hftbacktest/tree/master/rust) directory. Key Features[](https://hftbacktest.readthedocs.io/en/v1.8.4/#key-features "Permalink to this heading") -------------------------------------------------------------------------------------------------------- * Working in [Numba](https://numba.pydata.org/) JIT function. * Complete tick-by-tick simulation with a variable time interval. * Full order book reconstruction based on L2 feeds(Market-By-Price). * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. Getting started[](https://hftbacktest.readthedocs.io/en/v1.8.4/#getting-started "Permalink to this heading") -------------------------------------------------------------------------------------------------------------- ### Installation[](https://hftbacktest.readthedocs.io/en/v1.8.4/#installation "Permalink to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/v1.8.4/#data-source-format "Permalink to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/v1.8.4/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html) . ### A Quick Example[](https://hftbacktest.readthedocs.io/en/v1.8.4/#a-quick-example "Permalink to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def simple\_two\_sided\_quote(hbt, stat): max\_position \= 5 half\_spread \= hbt.tick\_size \* 20 skew \= 1 order\_qty \= 0.1 last\_order\_id \= \-1 order\_id \= 0 \# Checks every 0.1s while hbt.elapse(100\_000): \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders() \# Obtains the current mid-price and computes the reservation price. mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 reservation\_price \= mid\_price \- skew \* hbt.position \* hbt.tick\_size buy\_order\_price \= reservation\_price \- half\_spread sell\_order\_price \= reservation\_price + half\_spread last\_order\_id \= \-1 \# Cancel all outstanding orders for order in hbt.orders.values(): if order.cancellable: hbt.cancel(order.order\_id) last\_order\_id \= order.order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order cancellation responses is received. if last\_order\_id \>= 0: hbt.wait\_order\_response(last\_order\_id) \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders() last\_order\_id \= \-1 if hbt.position < max\_position: \# Submits a new post-only limit bid order. order\_id += 1 hbt.submit\_buy\_order( order\_id, buy\_order\_price, order\_qty, GTX ) last\_order\_id \= order\_id if hbt.position \> \-max\_position: \# Submits a new post-only limit ask order. order\_id += 1 hbt.submit\_sell\_order( order\_id, sell\_order\_price, order\_qty, GTX ) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: hbt.wait\_order\_response(last\_order\_id) \# Records the current state for stat calculation. stat.record(hbt) Examples[](https://hftbacktest.readthedocs.io/en/v1.8.4/#examples "Permalink to this heading") ------------------------------------------------------------------------------------------------ You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory. Contributing[](https://hftbacktest.readthedocs.io/en/v1.8.4/#contributing "Permalink to this heading") -------------------------------------------------------------------------------------------------------- Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: * Improve performance statistics reporting * Implement test code * Add additional queue or exchange models * Update documentation and examples [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#hftbacktest "Link to this heading") ==================================================================================================== [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.10-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.84-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#high-frequency-trading-backtesting-tool "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#key-features "Link to this heading") ------------------------------------------------------------------------------------------------------ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#documentation "Link to this heading") -------------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Getting started[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#getting-started "Link to this heading") ------------------------------------------------------------------------------------------------------------ ### Installation[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#tutorials "Link to this heading") ------------------------------------------------------------------------------------------------ * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) * [Level-3 Backtesting](https://hftbacktest.readthedocs.io/en/latest/tutorials/Level-3%20Backtesting.html) * [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) * [Queue-Based Market Making in Large Tick Size Assets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html) Examples[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#examples "Link to this heading") ---------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#migration-to-v2 "Link to this heading") ------------------------------------------------------------------------------------------------------------ Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#roadmap "Link to this heading") -------------------------------------------------------------------------------------------- Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/#contributing "Link to this heading") ------------------------------------------------------------------------------------------------------ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # HftBacktest — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#) * HftBacktest * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/index.rst.txt) * * * HftBacktest[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#hftbacktest "Link to this heading") ==================================================================================================== [![CodeQL](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push)](https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml) [![Python Version](https://shields.io/badge/python-3.10-blue)](https://www.python.org/) [![Package Version](https://badge.fury.io/py/hftbacktest.svg)](https://pypi.org/project/hftbacktest) [![Downloads](https://static.pepy.tech/badge/hftbacktest)](https://pepy.tech/project/hftbacktest) [![Rust Version](https://shields.io/badge/rustc-1.79-blue)](https://www.rust-lang.org/) [![Rust crates.io version](https://img.shields.io/crates/v/hftbacktest.svg)](https://crates.io/crates/hftbacktest) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/nkaz001/hftbacktest/blob/master/LICENSE) [![Documentation Status](https://readthedocs.org/projects/hftbacktest/badge/?version=latest)](https://hftbacktest.readthedocs.io/en/latest/?badge=latest) [![Roadmap](https://img.shields.io/badge/Roadmap-gray)](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) [![Github](https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social)](https://github.com/nkaz001/hftbacktest) High-Frequency Trading Backtesting Tool[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#high-frequency-trading-backtesting-tool "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#key-features "Link to this heading") ------------------------------------------------------------------------------------------------------ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. * Working in [Numba](https://numba.pydata.org/) JIT function (Python). * Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. * Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order (Rust-only, WIP) feeds. * Backtest accounting for both feed and order latency, using provided models or your own custom model. * Order fill simulation that takes into account the order queue position, using provided models or your own custom model. * Backtesting of multi-asset and multi-exchange models * Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#documentation "Link to this heading") -------------------------------------------------------------------------------------------------------- See [full document here](https://hftbacktest.readthedocs.io/) . Getting started[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#getting-started "Link to this heading") ------------------------------------------------------------------------------------------------------------ ### Installation[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#installation "Link to this heading") hftbacktest supports Python 3.10+. You can install hftbacktest using `pip`: pip install hftbacktest Or you can clone the latest development version from the Git repository with: git clone https://github.com/nkaz001/hftbacktest ### Data Source & Format[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#data-source-format "Link to this heading") Please see [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) . You can also find some data [here](https://reach.stratosphere.capital/data/usdm/) , hosted by the supporter. ### A Quick Example[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#a-quick-example "Link to this heading") Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: @njit def market\_making\_algo(hbt): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size lot\_size \= hbt.depth(asset\_no).lot\_size \# in nanoseconds while hbt.elapse(10\_000\_000) \== 0: hbt.clear\_inactive\_orders(asset\_no) a \= 1 b \= 1 c \= 1 hs \= 1 \# Alpha, it can be a combination of several indicators. forecast \= 0 \# In HFT, it can be various measurements of short-term market movements, \# such as the high-low range in the last X minutes. volatility \= 0 \# Delta risk, it can be a combination of several risks. position \= hbt.position(asset\_no) risk \= (c + volatility) \* position half\_spread \= (c + volatility) \* hs max\_notional\_position \= 1000 notional\_qty \= 100 depth \= hbt.depth(asset\_no) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 \# fair value pricing = mid\_price + a \* forecast \# or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast \# risk skewing = -b \* risk reservation\_price \= mid\_price + a \* forecast \- b \* risk new\_bid \= reservation\_price \- half\_spread new\_ask \= reservation\_price + half\_spread new\_bid\_tick \= min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick \= max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty \= np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size \# Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id \= \-1 update\_bid \= True update\_ask \= True buy\_limit\_exceeded \= position \* mid\_price \> max\_notional\_position sell\_limit\_exceeded \= position \* mid\_price < \-max\_notional\_position orders \= hbt.orders(asset\_no) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() if order.side \== BUY: if order.price\_tick \== new\_bid\_tick or buy\_limit\_exceeded: update\_bid \= False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id elif order.side \== SELL: if order.price\_tick \== new\_ask\_tick or sell\_limit\_exceeded: update\_ask \= False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id \= order.order\_id \# It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and \# have queue position. \# This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: \# There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id \= new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id if update\_ask: \# There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id \= new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: \# Waits for the order response for a maximum of 5 seconds. timeout \= 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#tutorials "Link to this heading") ------------------------------------------------------------------------------------------------ * [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) * [Getting Started](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html) * [Working with Market Depth and Trades](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html) * [Integrating Custom Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html) * [Making Multiple Markets - Introduction](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html) * [High-Frequency Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html) * [Impact of Order Latency](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html) * [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) * [Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html) * [Making Multiple Markets](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html) * [Risk Mitigation through Price Protection in Extreme Market Conditions](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html) Examples[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#examples "Link to this heading") ---------------------------------------------------------------------------------------------- You can find more examples in [examples](https://github.com/nkaz001/hftbacktest/tree/master/examples) directory and [Rust examples](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/) . ### The complete process of backtesting Binance Futures[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#the-complete-process-of-backtesting-binance-futures "Link to this heading") [high-frequency gridtrading](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/examples/gridtrading.ipynb) : The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#migration-to-v2 "Link to this heading") ------------------------------------------------------------------------------------------------------------ Please see the [migration guide](https://hftbacktest.readthedocs.io/en/latest/migration2.html) . Roadmap[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#roadmap "Link to this heading") -------------------------------------------------------------------------------------------- Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . Contributing[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/#contributing "Link to this heading") ------------------------------------------------------------------------------------------------------ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the [roadmap](https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md) . [Develop and launch modern apps with MongoDB Atlas, a resilient data platform.](https://server.ethicalads.io/proxy/click/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) [Ads by EthicalAds](https://www.ethicalads.io/advertisers/topics/data-science/?ref=ea-text) Close Ad ![](https://server.ethicalads.io/proxy/view/10123/019db0e6-07d2-75d1-b157-c747b6979e98/) --- # Data Preparation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Data Preparation * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Data%20Preparation.ipynb.txt) * * * Data Preparation[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html#Data-Preparation "Link to this heading") ============================================================================================================================================ To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself. Getting started from Binance Futures’ raw feed data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html#Getting-started-from-Binance-Futures'-raw-feed-data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ You can collect Binance Futures feed yourself using [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) . \[1\]: import gzip with gzip.open('usdm/btcusdt\_20240808.gz', 'r') as f: for i in range(5): line \= f.readline() print(line) b'1723161255030314667 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1723161256299,"T":1723161256298,"s":"BTCUSDT","U":5123107832006,"u":5123107837557,"pu":5123107831937,"b":\[\["58710.20","0.014"\],\["61496.50","0.010"\],\["61510.90","0.000"\],\["61641.50","1.211"\],\["61652.80","0.195"\],\["61653.30","0.072"\],\["61653.70","0.067"\],\["61657.90","0.067"\],\["61668.50","0.086"\],\["61670.60","0.161"\],\["61672.50","0.821"\],\["61673.60","0.048"\],\["61675.60","0.050"\],\["61684.50","0.765"\],\["61686.20","0.008"\],\["61701.80","0.331"\],\["61703.10","0.238"\],\["61715.90","0.308"\],\["61721.60","0.235"\],\["61724.10","0.002"\],\["61737.00","0.015"\],\["61739.00","0.000"\],\["61740.10","0.008"\],\["61740.50","12.111"\],\["61756.90","0.550"\],\["61758.70","0.003"\],\["61763.20","0.014"\],\["61764.10","0.168"\],\["61764.30","0.000"\],\["61765.50","0.000"\],\["61767.40","0.004"\],\["61768.20","0.120"\],\["61768.60","0.020"\],\["61768.90","0.099"\],\["61770.80","0.049"\],\["61771.10","0.612"\],\["61771.70","0.010"\],\["61773.50","0.035"\],\["61773.80","0.025"\],\["61774.00","0.112"\],\["61775.60","0.010"\],\["61776.00","0.084"\],\["61778.30","0.000"\],\["61778.60","0.408"\],\["61779.30","0.020"\],\["61779.60","0.220"\],\["61783.80","0.002"\],\["61784.90","0.102"\],\["61785.00","0.000"\],\["61788.10","0.140"\],\["61789.50","0.000"\],\["61798.70","0.153"\],\["61800.20","2.507"\]\],"a":\[\["61800.30","3.330"\],\["61804.60","0.057"\],\["61810.00","0.285"\],\["61812.00","0.732"\],\["61814.90","0.000"\],\["61817.20","0.000"\],\["61818.70","0.040"\],\["61824.00","0.860"\],\["61829.10","0.185"\],\["61831.30","0.008"\],\["61831.40","0.501"\],\["61839.00","0.002"\],\["61840.00","0.192"\],\["61856.30","0.003"\],\["61857.10","0.027"\],\["61857.40","0.000"\],\["61858.80","0.005"\],\["61858.90","0.032"\],\["61859.60","0.034"\],\["61874.80","0.006"\],\["61893.40","0.335"\],\["61911.90","0.014"\],\["61925.90","0.000"\],\["61930.50","0.015"\],\["61945.10","0.000"\],\["61953.70","0.000"\],\["62144.00","0.006"\],\["63113.70","0.000"\],\["65880.70","15.918"\]\]}}\\n' b'1723161255088169167 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107839020,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.510","T":1723161256313,"E":1723161256313}}\\n' b'1723161255088176367 {"stream":"btcusdt@trade","data":{"e":"trade","E":1723161256322,"T":1723161256322,"s":"BTCUSDT","t":5266583935,"p":"61800.30","q":"0.006","X":"MARKET","m":false}}\\n' b'1723161255088181667 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840008,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.504","T":1723161256322,"E":1723161256322}}\\n' b'1723161255088182467 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840016,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.522","T":1723161256322,"E":1723161256322}}\\n' The first token of the line is timestamp received by local. **Note:** The timestamp is in nanoseconds. The data needs to be converted to normalized data that can be fed into HftBacktest. `convert` method also attempts to correct timestamps by reordering the rows. \[2\]: import numpy as np from hftbacktest.data.utils import binancefutures data \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Normalized data as follows. You can find more details on [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) . \[3\]: import polars as pl pl.DataFrame(data) \[3\]: shape: (491\_973, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 58710.2 | 0.014 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61496.5 | 0.01 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61510.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61641.5 | 1.211 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61652.8 | 0.195 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1723161600030000000 | 1723161600043617932 | 62292.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161600319000000 | 1723161600370793433 | 5000.0 | 2.321 | 0 | 0 | 0.0 | | 3489660929 | 1723161600709000000 | 1723161600760777134 | 61659.8 | 0.981 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61631.7 | 0.283 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61632.6 | 0.0 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. \[4\]: \_ \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', output\_filename\='usdm/btcusdt\_20240808.npz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Saving to usdm/btcusdt\_20240808.npz Creating a market depth snapshot[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html#Creating-a-market-depth-snapshot "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As Binance Futures exchange runs 24/7, you need the initial snapshot to get the complete(almost) market depth. [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) fetches the snapshot only when it makes the connection, so you need build the initial snapshot from the start of the collected feed data. \[5\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. data \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001 ) Bid levels are shown before ask levels in the snapshot, and levels are sorted from the best price to the farthest price. \[6\]: pl.DataFrame(data) \[6\]: shape: (9\_597, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096388 | 0 | 0 | 61659.7 | 1.486 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61659.0 | 0.002 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.1 | 0.033 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.0 | 6.718 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61657.9 | 0.007 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660932 | 0 | 0 | 77354.3 | 0.015 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 77905.9 | 0.003 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 80000.0 | 10.708 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 104765.0 | 0.034 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 617050.0 | 0.003 | 0 | 0 | 0.0 | \[7\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240808\_eod.npz' ) \[8\]: \# Converts 20240809 data. \_ \= binancefutures.convert( 'usdm/btcusdt\_20240809.gz', output\_filename\='usdm/btcusdt\_20240809.npz', combined\_stream\=True ) \# Builds 20240809's last snapshot. \# Due to the file size limitation of GitHub, btcusdt\_20240809.npz does not contain data for the entire day. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz', initial\_snapshot\='usdm/btcusdt\_20240808\_eod.npz', ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1273873720 Correcting the event order Saving to usdm/btcusdt\_20240809.npz \[9\]: \# Builds 20240809's last snapshot without the initial snapshot. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last\_wo\_ss.npz' ) \# Builds the 20240809's last snapshot from 20240808 without the initial snapshot. \_ \= create\_last\_snapshot( \[\ 'usdm/btcusdt\_20240808.npz',\ 'usdm/btcusdt\_20240809.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz' ) Getting started from Tardis.dev data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html#Getting-started-from-Tardis.dev-data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Few vendors offer tick-by-tick full market depth data along with snapshot and trade data, and Tardis.dev is among them. **Note:** Some data may have an issue with the exchange timestamp. Ideally, the exchange timestamp should reflect the moment the event occurs at the matching engine. However, some data uses the server’s data sent timestamp instead of the matching engine timestamp. \[10\]: \# https://docs.tardis.dev/historical-data-details/binance-futures \# Downloads sample Binance futures BTCUSDT trades !wget https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_trades.csv.gz \# Downloads sample Binance futures BTCUSDT book !wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_book.csv.gz \--2024-08-09 09:42:51-- https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.6.96, 104.18.7.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.6.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3090479 (2.9M) \[text/csv\] Saving to: ‘BTCUSDT\_trades.csv.gz’ BTCUSDT\_trades.csv. 100%\[===================>\] 2.95M 5.66MB/s in 0.5s 2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s 2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp. \[11\]: from hftbacktest.data.utils import tardis data \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\] ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order \[12\]: pl.DataFrame(data) \[12\]: shape: (27\_532\_602, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 | | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 | | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 | | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: \_ \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000 ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don’t need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev’s SOD snapshot in the converted file using the option. --- # Working with Market Depth and Trades — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Working with Market Depth and Trades * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Working%20with%20Market%20Depth%20and%20Trades.ipynb.txt) * * * Working with Market Depth and Trades[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Working-with-Market-Depth-and-Trades "Link to this heading") ================================================================================================================================================================================================================ Display 3-depth[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-3-depth "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: from numba import njit @njit def print\_3depth(hbt): while hbt.elapse(60\_000\_000\_000) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = rice / tick\_size i \= 0 for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): qty \= depth.ask\_qty\_at\_tick(price\_tick) if qty \> 0: print( 'ask: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): qty \= depth.bid\_qty\_at\_tick(price\_tick) if qty \> 0: print( 'bid: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[2\]: import numpy as np btcusdt\_20240809 \= np.load('usdm/btcusdt\_20240809.npz')\['data'\] btcusdt\_20240808\_eod \= np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\] \[3\]: from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_3depth(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Efficient Market Depth Access[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Efficient-Market-Depth-Access "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `ROIVectorMarketDepth` provides more efficient market depth access through a vector that holds a limited price range of interest. The backtester using this feature can be created by `ROIVectorMarketDepthBacktest`. \[4\]: from numba import njit @njit def print\_3depth\_fast(hbt): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(60\_000\_000\_000) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = price / tick\_size i \= 0 for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): \# depth.ask\_depth returns the ask depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick qty \= depth.ask\_depth\[t\] if qty \> 0: print( 'ask: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): \# depth.bid\_depth returns the bid depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick qty \= depth.bid\_depth\[t\] if qty \> 0: print( 'bid: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[5\]: from hftbacktest import ROIVectorMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# Sets the lower bound price for the range of interest in the market depth. .roi\_lb(30000) \# Sets the upper bound price for the range of interest in the market depth. .roi\_ub(90000) ) \[6\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_3depth\_fast(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Order-Book-Imbalance "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def orderbookimbalance(hbt, out): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(10 \* 1e9) \== 0: depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 sum\_ask\_qty\_50bp \= 0.0 sum\_ask\_qty \= 0.0 for price\_tick in range(depth.best\_ask\_tick, roi\_ub\_tick + 1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick ask\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (ask\_price \- mid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_ask\_qty += depth.ask\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_ask\_qty\_50bp \= sum\_ask\_qty sum\_bid\_qty\_50bp \= 0.0 sum\_bid\_qty \= 0.0 for price\_tick in range(depth.best\_bid\_tick, roi\_lb\_tick \- 1, \-1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick bid\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (mid\_price \- bid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_bid\_qty += depth.bid\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_bid\_qty\_50bp \= sum\_bid\_qty imbalance\_50bp \= sum\_bid\_qty\_50bp \- sum\_ask\_qty\_50bp imbalance\_1pct \= sum\_bid\_qty \- sum\_ask\_qty imbalance\_tob \= depth.bid\_depth\[depth.best\_bid\_tick \- roi\_lb\_tick\] \- depth.ask\_depth\[depth.best\_ask\_tick \- roi\_lb\_tick\] out.append((hbt.current\_timestamp, imbalance\_tob, imbalance\_50bp, imbalance\_1pct)) return True \[8\]: from numba.typed import List from numba.types import Tuple, float64 hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) orderbookimbalance(hbt, out) \_ \= hbt.close() \[9\]: import polars as pl df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', 'TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[9\]: shape: (30, 4) | Local Timestamp | TOB Imbalance | 0.5% Imbalance | 1% Imbalance | | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 2.729 | \-1748.101 | \-3908.736 | | 2024-08-09 00:00:21.500 | 4.623 | \-1749.435 | \-3512.845 | | 2024-08-09 00:00:31.500 | \-6.465 | \-1259.897 | \-3357.755 | | 2024-08-09 00:00:41.500 | \-7.922 | \-1174.185 | \-3471.955 | | 2024-08-09 00:00:51.500 | \-2.484 | \-1147.597 | \-3461.48 | | … | … | … | … | | 2024-08-09 00:04:21.500 | 3.828 | \-1186.236 | \-3551.78 | | 2024-08-09 00:04:31.500 | \-1.35 | \-1332.379 | \-3517.854 | | 2024-08-09 00:04:41.500 | \-3.754 | \-1166.521 | \-2693.672 | | 2024-08-09 00:04:51.500 | \-2.525 | \-1188.56 | \-2716.914 | | 2024-08-09 00:05:01.500 | 1.91 | \-594.991 | \-2138.82 | \[10\]: from matplotlib import pyplot pyplot.plot(df\['Local Timestamp'\], df\['TOB Imbalance'\]) pyplot.plot(df\['Local Timestamp'\], df\['0.5% Imbalance'\]) pyplot.plot(df\['Local Timestamp'\], df\['1% Imbalance'\]) \[10\]: \[\] ![../_images/tutorials_Working_with_Market_Depth_and_Trades_13_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Working_with_Market_Depth_and_Trades_13_1.png) Display last trades between the step[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-last-trades-between-the-step "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[11\]: from hftbacktest import BUY\_EVENT @njit def print\_trades(hbt): while hbt.elapse(60 \* 1e9) \== 0: print('-------------------------------------------------------------------------------') print('current\_timestamp:', hbt.current\_timestamp) \# Gets the last trades occurring in the market, not the trades of our orders. last\_trades \= hbt.last\_trades(0) num \= 0 for last\_trade in last\_trades: if num \> 10: print('...') break print( 'exch\_timestamp:', last\_trade.exch\_ts, 'buy' if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT else 'sell', last\_trade.qty, '@', last\_trade.px ) num += 1 \# To prevent accumulating all last trades, which may cause a slowdown, \# clear\_last\_trades needs to be called. \# After this, accessing \`last\_trades\` will cause a crash. hbt.clear\_last\_trades(0) return True \[12\]: asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# To retrieve the last trades, \`last\_trades\_capacity\` should be set. .last\_trades\_capacity(1000) .roi\_lb(30000) .roi\_ub(90000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_trades(hbt) \_ \= hbt.close() \------------------------------------------------------------------------------- current\_timestamp: 1723161661500000000 exch\_timestamp: 1723161602372000000 buy 0.489 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.198 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.006 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.011 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.238 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.007 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.005 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161721500000000 exch\_timestamp: 1723161661697000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661724000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661751000000 buy 0.135 @ 61594.2 exch\_timestamp: 1723161661806000000 sell 1.328 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.006 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.32 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.032 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 1.208 @ 61594.1 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161781500000000 exch\_timestamp: 1723161721541000000 sell 0.002 @ 61576.5 exch\_timestamp: 1723161721574000000 buy 0.012 @ 61576.6 exch\_timestamp: 1723161721578000000 sell 0.003 @ 61576.5 exch\_timestamp: 1723161721583000000 buy 0.275 @ 61576.6 exch\_timestamp: 1723161721583000000 buy 0.469 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.095 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.102 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.197 @ 61576.6 exch\_timestamp: 1723161721586000000 buy 0.13 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.425 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.324 @ 61576.6 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161841500000000 exch\_timestamp: 1723161781628000000 sell 0.026 @ 61629.6 exch\_timestamp: 1723161781727000000 buy 0.011 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.05 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.006 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.007 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.075 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.065 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.247 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161901500000000 exch\_timestamp: 1723161841561000000 buy 0.01 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.006 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.002 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.022 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.097 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.014 @ 61621.6 exch\_timestamp: 1723161841565000000 buy 0.003 @ 61621.6 exch\_timestamp: 1723161841613000000 buy 0.002 @ 61622.5 exch\_timestamp: 1723161841613000000 buy 0.003 @ 61622.6 ... Rolling Volume-Weighted Average Price[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Rolling-Volume-Weighted-Average-Price "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[13\]: @njit def rolling\_vwap(hbt, out): buy\_amount\_bin \= np.zeros(100\_000, np.float64) buy\_qty\_bin \= np.zeros(100\_000, np.float64) sell\_amount\_bin \= np.zeros(100\_000, np.float64) sell\_qty\_bin \= np.zeros(100\_000, np.float64) idx \= 0 last\_trade\_price \= np.nan while hbt.elapse(10 \* 1e9) \== 0: last\_trades \= hbt.last\_trades(0) for last\_trade in last\_trades: if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT: buy\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty buy\_qty\_bin\[idx\] += last\_trade.qty else: sell\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty sell\_qty\_bin\[idx\] += last\_trade.qty hbt.clear\_last\_trades(0) idx += 1 if idx \>= 1: vwap10sec \= np.divide( buy\_amount\_bin\[idx \- 1\] + sell\_amount\_bin\[idx \- 1\], buy\_qty\_bin\[idx \- 1\] + sell\_qty\_bin\[idx \- 1\] ) else: vwap10sec \= np.nan if idx \>= 6: vwap1m \= np.divide( np.sum(buy\_amount\_bin\[idx \- 6:idx\]) + np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\]) + np.sum(sell\_qty\_bin\[idx \- 6:idx\]) ) buy\_vwap1m \= np.divide(np.sum(buy\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\])) sell\_vwap1m \= np.divide(np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(sell\_qty\_bin\[idx \- 6:idx\])) else: vwap1m \= np.nan buy\_vwap1m \= np.nan sell\_vwap1m \= np.nan out.append((hbt.current\_timestamp, vwap10sec, vwap1m, buy\_vwap1m, sell\_vwap1m)) return True \[14\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) rolling\_vwap(hbt, out) \_ \= hbt.close() \[15\]: df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', '10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[15\]: shape: (30, 5) | Local Timestamp | 10-sec VWAP | 1-min VWAP | 1-min Buy VWAP | 1-min Sell VWAP | | --- | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 61687.182976 | NaN | NaN | NaN | | 2024-08-09 00:00:21.500 | 61709.337576 | NaN | NaN | NaN | | 2024-08-09 00:00:31.500 | 61697.538054 | NaN | NaN | NaN | | 2024-08-09 00:00:41.500 | 61663.958879 | NaN | NaN | NaN | | 2024-08-09 00:00:51.500 | 61637.340621 | NaN | NaN | NaN | | … | … | … | … | … | | 2024-08-09 00:04:21.500 | 61643.009847 | 61624.459011 | 61626.495542 | 61622.549429 | | 2024-08-09 00:04:31.500 | 61670.795685 | 61635.877251 | 61638.362314 | 61632.48854 | | 2024-08-09 00:04:41.500 | 61643.108582 | 61641.846489 | 61648.672337 | 61636.032054 | | 2024-08-09 00:04:51.500 | 61614.723569 | 61640.490841 | 61647.769844 | 61634.372128 | | 2024-08-09 00:05:01.500 | 61584.697467 | 61637.334102 | 61642.209551 | 61632.12064 | \[16\]: pyplot.plot(df\['Local Timestamp'\], df\['10-sec VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min Buy VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min Sell VWAP'\]) \[16\]: \[\] ![../_images/tutorials_Working_with_Market_Depth_and_Trades_21_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Working_with_Market_Depth_and_Trades_21_1.png) --- # High-Frequency Grid Trading — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * High-Frequency Grid Trading * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/High-Frequency%20Grid%20Trading.ipynb.txt) * * * High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading "Link to this heading") =============================================================================================================================================================================== **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Plain High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#Plain-High-Frequency-Grid-Trading "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is a high-frequency version of Grid Trading that keeps posting orders on grids centered around the mid-price, maintaining a fixed interval and a set number of grids. \[1\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT @njit def gridtrading(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# Aligns the prices to the grid. bid\_price \= np.floor((mid\_price \- half\_spread) / grid\_interval) \* grid\_interval ask\_price \= np.ceil((mid\_price + half\_spread) / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True For generating order latency from the feed data file, which uses feed latency as order latency, please see [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) . \[2\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) \[3\]: %%time gridtrading(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 6min 5s, sys: 9.08 s, total: 6min 15s Wall time: 6min 16s \[4\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.265693 | 25.144025 | 0.082691 | 0.021906 | 9489.819672 | 127.266294 | 3.774836 | 0.00013 | 9140.288 | \[5\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_7_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_7_0.png) High-Frequency Grid Trading with Skewing[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading-with-Skewing "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- By incorporating position-based skewing, the strategy’s risk-adjusted returns can be improved. \[6\]: @njit def gridtrading(hbt, recorder, skew): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* tick\_size \* position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True ### Weak skew[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#Weak-skew "Link to this heading") \[7\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 1 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[7\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.363916 | 25.321583 | 0.060482 | 0.014831 | 10563.644529 | 141.707178 | 4.077966 | 0.000085 | 9409.12 | \[8\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_12_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_12_0.png) ### Strong skew[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#Strong-skew "Link to this heading") Under strong skew, the position is more limited compared to the weak skew case. You may also observe a spike in equity when the market moves sharply. However, in reality, this might not be realized due to order latency. Later, we will explore the impact of order latency and highlight the importance of using actual historical order latency data. \[9\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 10 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[9\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 27.282302 | 47.25453 | 0.042574 | 0.005391 | 11838.874048 | 158.842253 | 7.897853 | 0.000054 | 8270.01 | \[10\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_15_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_15_0.png) Multiple Assets[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading.html#Multiple-Assets "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------- You might need to find the proper parameters for each asset to achieve better performance. As an example, here it uses single parameters set to demonstrate how the performance of a combination of multiple assets will be. \[11\]: @njit def gridtrading(hbt, recorder, half\_spread, grid\_interval, skew, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= grid\_num \* order\_qty \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Ensures the grid interval aligns with the tick size, with the minimum set to the tick size. grid\_interval \= max(np.round(grid\_interval / tick\_size) \* tick\_size, tick\_size) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[12\]: from hftbacktest import BUY\_EVENT, SELL\_EVENT latency\_data \= np.concatenate( \[np.load('latency/live\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .log\_prob\_queue\_model2() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] half\_spread \= mid\_price \* 0.0008 grid\_interval \= mid\_price \* 0.0008 skew \= mid\_price \* 0.000025 recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, skew, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_{}.npz'.format(asset\_name)) \[13\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[14\]: import polars as pl from hftbacktest.stats import LinearAssetRecord equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[15\]: from matplotlib import pyplot as plt fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 10 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[15\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_21_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_21_1.png) --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Making Multiple Markets - Introduction * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb.txt) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Link to this heading") ==================================================================================================================================================================================================================== One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Level-3 Backtesting — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Level-3 Backtesting * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Level-3%20Backtesting.ipynb.txt) * * * Level-3 Backtesting[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Level-3%20Backtesting.html#Level-3-Backtesting "Link to this heading") ===================================================================================================================================================== The Level-3 feed data for HftBacktest is built from DataBento’s CME Market-By-Order data \[1\]: from hftbacktest.data.utils import databento for date in range(20240609, 20240615): databento.convert(f'data/db/glbx-mdp3-{date}.mbo.dbn.zst', 'BTCM4', output\_filename\=f'data/BTCM4\_{date}\_l3.npz') Correcting the latency Correcting the event order Saving to data/BTCM4\_20240609\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240610\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240611\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240612\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240613\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240614\_l3.npz \[2\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTC, LIMIT @njit def gridtrading(hbt, recorder, skew): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 10 max\_position \= 5 grid\_interval \= tick\_size \* 1 half\_spread \= tick\_size \* 0.4 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf alpha \= 0 reservation\_price \= mid\_price + alpha \- skew \* tick\_size \* position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTC, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTC, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[3\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/BTCM4\_20240609\_l3.npz',\ 'data/BTCM4\_20240610\_l3.npz',\ 'data/BTCM4\_20240611\_l3.npz',\ 'data/BTCM4\_20240612\_l3.npz',\ 'data/BTCM4\_20240613\_l3.npz',\ 'data/BTCM4\_20240614\_l3.npz',\ \]) .linear\_asset(5) .constant\_latency(100\_000, 100\_000) .l3\_fifo\_queue\_model() .no\_partial\_fill\_exchange() .trading\_qty\_fee\_model(5, 5) .tick\_size(5) .lot\_size(1) .roi\_lb(0.0) .roi\_ub(100000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_0000) \[4\]: %%time gridtrading(hbt, recorder.recorder, 0.5) \_ \= hbt.close() CPU times: user 35.6 s, sys: 336 ms, total: 35.9 s Wall time: 32.4 s \[5\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).contract\_size(5).stats(book\_size\=1\_000\_000) l3\_backtest\_equity \= stats.entire\['equity\_wo\_fee'\] stats.plot() ![../_images/tutorials_Level-3_Backtesting_5_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Level-3_Backtesting_5_0.png) The following code constructs Level-2 data from Level-3 data for the purpose of comparing backtesting results between Level-3 and Level-2. Level-2 estimates queue positions using a model, whereas Level-3 determines queue positions directly from the order data. \[6\]: from hftbacktest.data import correct\_event\_order, validate\_event\_order from hftbacktest import ( EXCH\_EVENT, LOCAL\_EVENT, TRADE\_EVENT, DEPTH\_EVENT, DEPTH\_CLEAR\_EVENT, ADD\_ORDER\_EVENT, MODIFY\_ORDER\_EVENT, CANCEL\_ORDER\_EVENT, FILL\_EVENT, BUY\_EVENT, SELL\_EVENT, event\_dtype ) from numba.experimental import jitclass from numba.types import DictType, int64 @jitclass class L3MarketDepth: bid\_depth: DictType(int64, float64) ask\_depth: DictType(int64, float64) order\_book\_px: DictType(uint64, float64) order\_book\_qty: DictType(uint64, float64) tick\_size: float64 def \_\_init\_\_(self, tick\_size): self.bid\_depth \= Dict.empty(int64, float64) self.ask\_depth \= Dict.empty(int64, float64) self.order\_book\_px \= Dict.empty(uint64, float64) self.order\_book\_qty \= Dict.empty(uint64, float64) self.tick\_size \= tick\_size def add\_order(self, ev): if ev.order\_id in self.order\_book\_qty: print('add\_order: OrderIdExist', ev.order\_id) raise ValueError self.order\_book\_px\[ev.order\_id\] \= ev.px; l2\_ev \= np.empty(1, event\_dtype) l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT price\_tick \= int(round(ev.px / self.tick\_size)) if ev.ev & BUY\_EVENT \== BUY\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= ev.qty; if price\_tick not in self.bid\_depth: self.bid\_depth\[price\_tick\] \= 0.0 self.bid\_depth\[price\_tick\] += ev.qty l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= \-ev.qty; if price\_tick not in self.ask\_depth: self.ask\_depth\[price\_tick\] \= 0.0 self.ask\_depth\[price\_tick\] += ev.qty l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[0\] def modify\_order(self, ev): if ev.order\_id not in self.order\_book\_qty: print('modify\_order: OrderNotFound', ev.order\_id) raise ValueError prev\_px \= self.order\_book\_px\[ev.order\_id\] prev\_qty \= self.order\_book\_qty\[ev.order\_id\] l2\_ev \= np.empty(2, event\_dtype) l2\_ev\[1\] \= l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT n \= 0 if prev\_qty \> 0: price\_tick \= int(round(prev\_px / self.tick\_size)) self.bid\_depth\[price\_tick\] \-= prev\_qty if int(round(prev\_px / self.tick\_size)) != int(round(ev.px / self.tick\_size)): l2\_ev\[0\].px \= prev\_px l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) n \= 1 elif prev\_qty < 0: price\_tick \= int(round(prev\_px / self.tick\_size)) self.ask\_depth\[price\_tick\] \-= np.abs(prev\_qty) if int(round(prev\_px / self.tick\_size)) != int(round(ev.px / self.tick\_size)): l2\_ev\[0\].px \= prev\_px l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) n \= 1 self.order\_book\_px\[ev.order\_id\] \= ev.px; price\_tick \= int(round(ev.px / self.tick\_size)) if ev.ev & BUY\_EVENT \== BUY\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= ev.qty; if price\_tick not in self.bid\_depth: self.bid\_depth\[price\_tick\] \= 0.0 self.bid\_depth\[price\_tick\] += ev.qty l2\_ev\[n\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= \-ev.qty; if price\_tick not in self.ask\_depth: self.ask\_depth\[price\_tick\] \= 0.0 self.ask\_depth\[price\_tick\] += ev.qty l2\_ev\[n\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[:n + 1\] def cancel\_order(self, ev): if ev.order\_id not in self.order\_book\_qty: print('cancel\_order: OrderNotFound', ev.order\_id, ev) raise ValueError del self.order\_book\_px\[ev.order\_id\] del self.order\_book\_qty\[ev.order\_id\] l2\_ev \= np.empty(1, event\_dtype) l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT if ev.ev & BUY\_EVENT \== BUY\_EVENT: price\_tick \= int(round(ev.px / self.tick\_size)) self.bid\_depth\[price\_tick\] \-= ev.qty l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: price\_tick \= int(round(ev.px / self.tick\_size)) self.ask\_depth\[price\_tick\] \-= ev.qty l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[0\] def clear(self): self.order\_book\_px.clear() self.order\_book\_qty.clear() self.bid\_depth.clear() self.ask\_depth.clear() @njit def convert\_l3\_to\_l2(data, tick\_size): result \= np.empty(len(data) \* 4, event\_dtype) local\_md \= L3MarketDepth(tick\_size) exch\_md \= L3MarketDepth(tick\_size) rn \= 0 for i in range(len(data)): if data\[i\].ev & (EXCH\_EVENT | LOCAL\_EVENT) \== EXCH\_EVENT | LOCAL\_EVENT: if data\[i\].ev & 0xff \== ADD\_ORDER\_EVENT: result\[rn\] \= exch\_md.add\_order(data\[i\]) rn += 1 elif data\[i\].ev & 0xff \== MODIFY\_ORDER\_EVENT: l2\_ev \= exch\_md.modify\_order(data\[i\]) result\[rn\] \= l2\_ev\[0\] rn += 1 if len(l2\_ev) \== 2: result\[rn\] \= l2\_ev\[1\] rn += 1 elif data\[i\].ev & 0xff \== CANCEL\_ORDER\_EVENT: result\[rn\] \= exch\_md.cancel\_order(data\[i\]) rn += 1 elif data\[i\].ev & 0xff \== FILL\_EVENT: continue elif data\[i\].ev & 0xff \== DEPTH\_CLEAR\_EVENT: exch\_md.clear() result\[rn\] \= data\[i\] rn += 1 else: result\[rn\] \= data\[i\] rn += 1 else: \# DataBento's CME data is aligned in both local and exchange timestamps. raise ValueError return result\[:rn\] \[7\]: for date in range(20240609, 20240615): l3 \= databento.convert(f'data/db/glbx-mdp3-{date}.mbo.dbn.zst', 'BTCM4') tick\_size \= 5 l2 \= convert\_l3\_to\_l2(l3, tick\_size) data \= correct\_event\_order( l2, np.argsort(l2\['exch\_ts'\], kind\='mergesort'), np.argsort(l2\['local\_ts'\], kind\='mergesort') ) validate\_event\_order(data) np.savez\_compressed(f'data/BTCM4\_{date}\_l2.npz', data\=data) Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order \[8\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/BTCM4\_20240609\_l2.npz',\ 'data/BTCM4\_20240610\_l2.npz',\ 'data/BTCM4\_20240611\_l2.npz',\ 'data/BTCM4\_20240612\_l2.npz',\ 'data/BTCM4\_20240613\_l2.npz',\ 'data/BTCM4\_20240614\_l2.npz',\ \]) .linear\_asset(5) .constant\_latency(100\_000, 100\_000) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_qty\_fee\_model(5, 5) .tick\_size(5) .lot\_size(1) .roi\_lb(0.0) .roi\_ub(100000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_0000) \[9\]: %%time gridtrading(hbt, recorder.recorder, 0.5) \_ \= hbt.close() CPU times: user 28.9 s, sys: 401 ms, total: 29.3 s Wall time: 24.9 s \[10\]: stats \= LinearAssetRecord(recorder.get(0)).contract\_size(5).stats(book\_size\=1\_000\_000) l2\_backtest\_equity \= stats.entire\['equity\_wo\_fee'\] stats.plot() ![../_images/tutorials_Level-3_Backtesting_11_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Level-3_Backtesting_11_0.png) \[11\]: from matplotlib import pyplot as plt plt.plot(l3\_backtest\_equity) plt.plot(l2\_backtest\_equity) plt.legend(\['Level-3', 'Level-2'\]) \[11\]: ![../_images/tutorials_Level-3_Backtesting_12_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Level-3_Backtesting_12_1.png) The impact of the difference can vary depending on the characteristics of the strategy; for some strategies, Level-2 estimation may be sufficiently accurate, while for others, it may not be. This comparison is intended to highlight these differences. In markets that only provide Level-2 data, it is important to develop a realistic queue position model based on live trading data. Although Level-3 data offers direct order queue position information, it is still crucial to validate backtesting results against live trading results. For example, in this CME Level-3 backtest, the market depth doesn’t include implied orders. --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on Level-2 Market-By-Price and Level-3 Market-By-Order feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Tutorials you’ll likely find interesting: \* \`High-Frequency Grid Trading - Simplified from GLFT \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Market Making with Alpha - APT \`\_ \* \`Accelerated Backtesting \`\_ \* \`Pricing Framework \`\_ Why Accurate Backtesting Matters — Not Just Conservative Approach ================================================================= Trading is a highly competitive field where only the small edges usually exist, but they can still make a significant difference. Because of this, backtesting must accurately simulate real-world conditions.: It should neither rely on an overly pessimistic approach that hides these small edges and profit opportunities, nor on an overly optimistic one that overstates them through unrealistic simulation. Or at the very least, you should clearly understand what differs from live trading and by how much, since sometimes fully accurate backtesting is not practical due to the time it requires. This is not about overfitting at the start—before you even consider issues like overfitting, you need confidence that your backtesting truly reflects real-world execution. For example, if you run a live trading strategy in January 2025, the backtest for that exact period should produce results that closely align with the actual results. Once you’ve validated that your backtesting can accurately reproduce live trading results, then you can proceed to deeper research, optimization, and considerations around overfitting. Accurate backtesting is the foundation. Without it, all further analysis—whether conservative or aggressive—becomes unreliable. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`High-Frequency Grid Trading - Comparison Across Other Exchanges \`\_ \* \`High-Frequency Grid Trading - Simplified from GLFT \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ \* \`Level-3 Backtesting \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Market Making with Alpha - Basis \`\_ \* \`Market Making with Alpha - APT \`\_ \* \`Queue-Based Market Making in Large Tick Size Assets \`\_ \* \`Fusing Depth Data \`\_ \* \`Accelerated Backtesting \`\_ \* \`Pricing Framework \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.11+-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.90-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/High-Frequency Grid Trading - Comparison Across Other Exchanges tutorials/High-Frequency Grid Trading - Simplified from GLFT tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/Level-3 Backtesting tutorials/Market Making with Alpha - Order Book Imbalance tutorials/Market Making with Alpha - Basis tutorials/Market Making with Alpha - APT tutorials/Queue-Based Market Making in Large Tick Size Assets tutorials/Fusing Depth Data tutorials/Accelerated Backtesting tutorials/Pricing Framework tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies Market Maker Program .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Impact of Order Latency — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Impact of Order Latency * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Impact%20of%20Order%20Latency.ipynb.txt) * * * Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html#Impact-of-Order-Latency "Link to this heading") ===================================================================================================================================================================== This example illustrates the impact of order latency on the performance of the strategy. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, order\_qty, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) Order Latency from Feed Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Feed-Latency "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Please see [the tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) on generating artificial order latency data from feed latency. \[2\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20230401.npz',\ 'latency/feed\_latency\_20230402.npz',\ 'latency/feed\_latency\_20230403.npz',\ 'latency/feed\_latency\_20230404.npz',\ 'latency/feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[2\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.197608 | \-0.224204 | \-0.001021 | 0.060794 | 4459.903239 | 328.415763 | \-0.016794 | \-6.2176e-7 | 75431.07 | \[3\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_4_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Impact_of_Order_Latency_4_0.png) Live Order Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html#Live-Order-Latency "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230401, 20230406)\] ) asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | 1.536293 | 1.741565 | 0.007814 | 0.051916 | 4563.105627 | 336.150295 | 0.150518 | 0.000005 | 67694.55 | \[5\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_7_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Impact_of_Order_Latency_7_0.png) Order Latency from Amplified Feed Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Amplified-Feed-Latency "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Order entry latency is 4 times the feed latency and order response latency is 3 times the feed latency. \[6\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/amp\_feed\_latency\_20230401.npz',\ 'latency/amp\_feed\_latency\_20230402.npz',\ 'latency/amp\_feed\_latency\_20230403.npz',\ 'latency/amp\_feed\_latency\_20230404.npz',\ 'latency/amp\_feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[6\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.376802 | \-0.430111 | \-0.002163 | 0.053785 | 4366.301072 | 321.501683 | \-0.040224 | \-0.000001 | 75711.93 | \[7\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_10_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Impact_of_Order_Latency_10_0.png) --- # Market Making with Alpha - Order Book Imbalance — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Market Making with Alpha - Order Book Imbalance * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.ipynb.txt) * * * Market Making with Alpha - Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Market-Making-with-Alpha---Order-Book-Imbalance "Link to this heading") ===================================================================================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Order Book Imbalance, also known as Order Flow Imbalance, is a widely recognized microstructure indicator often analyzed alongside trade flow. This concept has several derivatives, including the Micro-Price, VAMP (Volume Adjusted Mid Price), Weighted-Depth Order Book Price, and Static Order Book Imbalance, among others. * Static Order Book Imbalance Static Order Book Imbalance\=∑iNQbidi−∑iNQaski∑iNQbidi+∑iNQaski Through standardization, an alternative expression for order imbalance can be obtained, one that does not include the normalization numerator. Standardized Order Book Imbalance\=standardize(∑iNQbidi−∑iNQaski) * Volume Adjusted Mid Price Be aware that price and quantity are cross-multiplied between bid and ask sides. > VAMPbbo\=Pbest bid×Qbest ask+Pbest ask×Qbest bidQbest bid+Qbest ask > > VAMPN\=∑iNPbidi×Qaski+∑iNPaski×Qbidi∑iNQbidi+∑iNQaski > > where N is usually defined as a percentage of the mid price. For 1% market depth, compute bid side down to mid × 0.99 and ask side up to mid × 1.01. * Weighted-Depth Order Book Price Be aware that price and quantity are multiplied within the same side > Weighted−Depth Order Book Price\=∑iNPbidi×Qbidi+∑iNPaski×Qaski∑iNQbidi+∑iNQaski > > In this case, N—unlike in VAMP—is defined by a fixed total quantity. For 500 qty of market depth, compute bid side down to its total quantity reachs 500 and ask side up to its total quantity reachs 500. Alternatively, you can use notional value instead of quantity. * Another variation is to combine VAMP with Weighted-Depth Order Book Price. Peffective bidN\=∑iNPbidi×Qbidi∑iNQbidi Peffective askN\=∑iNPaski×Qaski∑iNQaski Qeffective bidN\=∑iNQbidi Qeffective askN\=∑iNQaski VAMPeffectiveN\=Peffective bidN×Qeffective askN+Peffective askN×Qeffective bidNQeffective bidN+Qeffective askN where N is defined just like VAMP. You can also apply other time-series techniques or statistical adjustments to these derived order book indicators, such as standardization. Extensive information on these indicators is available online. In the following examples, we begin by testing the standardized order book imbalance and then evaluate the other indicators in subsequent examples. ### Reference[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Reference "Link to this heading") * [The Micro-Price: A High Frequency Estimator of Future Prices](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2970694) * [Mind the Gaps: Short-Term Crypto Price Prediction](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4351947) * [Market microstructure signals](https://blog.headlandstech.com/2017/08/) **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def obi\_mm( hbt, stat, half\_spread, skew, c1, looking\_depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ): asset\_no \= 0 imbalance\_timeseries \= np.full(30\_000\_000, np.nan, np.float64) tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size t \= 0 roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 sum\_ask\_qty \= 0.0 from\_tick \= max(depth.best\_ask\_tick, roi\_lb\_tick) upto\_tick \= min(int(np.floor(mid\_price \* (1 + looking\_depth) / tick\_size)), roi\_ub\_tick) for price\_tick in range(from\_tick, upto\_tick): sum\_ask\_qty += depth.ask\_depth\[price\_tick \- roi\_lb\_tick\] sum\_bid\_qty \= 0.0 from\_tick \= min(depth.best\_bid\_tick, roi\_ub\_tick) upto\_tick \= max(int(np.ceil(mid\_price \* (1 \- looking\_depth) / tick\_size)), roi\_lb\_tick) for price\_tick in range(from\_tick, upto\_tick, \-1): sum\_bid\_qty += depth.bid\_depth\[price\_tick \- roi\_lb\_tick\] imbalance\_timeseries\[t\] \= sum\_bid\_qty \- sum\_ask\_qty \# Standardizes the order book imbalance timeseries for a given window m \= np.nanmean(imbalance\_timeseries\[max(0, t + 1 \- window):t + 1\]) s \= np.nanstd(imbalance\_timeseries\[max(0, t + 1 \- window):t + 1\]) alpha \= np.divide(imbalance\_timeseries\[t\] \- m, s) #-------------------------------------------------------- \# Computes bid price and ask price. order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) fair\_price \= mid\_price + c1 \* alpha normalized\_position \= position / order\_qty reservation\_price \= fair\_price \- skew \* normalized\_position bid\_price \= min(np.round(reservation\_price \- half\_spread), best\_bid) ask\_price \= max(np.round(reservation\_price + half\_spread), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) t += 1 if t \>= len(imbalance\_timeseries): raise Exception \# Records the current state for stat calculation. stat.record(hbt) \[2\]: %%time roi\_lb \= 10000 roi\_ub \= 50000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/btcusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/btcusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 80 skew \= 3.5 c1 \= 160 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_btcusdt.npz') CPU times: user 32min 23s, sys: 43.9 s, total: 33min 7s Wall time: 33min 5s \[3\]: data \= np.load('stats/obi\_btcusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[3\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-30 23:55:00 | 10.829336 | 13.5994 | 0.342371 | 0.037249 | 4119.876838 | 82.397448 | 9.191522 | 0.000139 | 2.6383e6 | \[4\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_4_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_4_0.png) \[5\]: %%time roi\_lb \= 0 roi\_ub \= 3000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/ethusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/ethusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 5 skew \= 0.2 c1 \= 10 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_ethusdt.npz') CPU times: user 27min 37s, sys: 38.3 s, total: 28min 15s Wall time: 28min 16s \[6\]: data \= np.load('stats/obi\_ethusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[6\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-31 23:55:00 | 9.017874 | 11.140311 | 0.299582 | 0.055187 | 4112.621933 | 82.252375 | 5.428451 | 0.000118 | 2.6036e6 | \[7\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_7_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_7_0.png) Another approach is to generate trading volume to qualify as a market maker and receive rebates. This strategy involves maintaining a high skew and tight spread. While the strategy itself may not be profitable or may incur losses, it can help achieve market maker status. \[9\]: %%time roi\_lb \= 10000 roi\_ub \= 50000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/btcusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/btcusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 10 skew \= 2 c1 \= 20 depth \= 0.001 \# 0.1% from the mid price interval \= 500\_000\_000 \# 500ms window \= 600\_000\_000\_000 / interval \# 10min order\_qty\_dollar \= 25\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) recorder.to\_npz('stats/obi\_vg\_btcusdt.npz') CPU times: user 30min 13s, sys: 44.1 s, total: 30min 57s Wall time: 31min 2s \[10\]: data \= np.load('stats/obi\_vg\_btcusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[10\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-30 23:55:00 | 14.0088 | 17.366641 | 0.129901 | 0.00928 | 8368.135201 | 209.203461 | 13.998514 | 0.000021 | 536859.7998 | \[11\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_11_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_11_0.png) Update on Backtesting Results for 2025[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Update-on-Backtesting-Results-for-2025 "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Updated backtesting results for February 2025 using the same parameters as in May 2023, except for the `power_prob_queue_model` parameter, which was changed from 2 to 3 to reflect market changes and a more challenging fill in the queue. \[13\]: import datetime start\_date \= datetime.datetime.strptime('20250101', '%Y%m%d') end\_date \= datetime.datetime.strptime('20250228', '%Y%m%d') \[14\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 80 skew \= 3.5 c1 \= 160 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_btcusdt\_2025.npz') CPU times: user 1h 56min 52s, sys: 8min 33s, total: 2h 5min 25s Wall time: 1h 29min 36s You can see from the following report that the order book imbalance continues to work consistently. However, the return per trade drops from 0.0139% to 0.0086%, including the 0.005% rebates. This again highlights the importance of rebates and the corresponding fee structure for market makers. \[16\]: data \= np.load('stats/obi\_btcusdt\_2025.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[16\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 5.369875 | 7.198137 | 0.459649 | 0.097893 | 4533.741393 | 90.675217 | 4.695446 | 0.000086 | 2.6075e6 | \[17\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_17_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_17_0.png) \[2\]: data \= np.load('stats/obi\_btcusdt\_202505.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[2\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-05-01 00:00:00 | 2025-07-31 23:55:00 | 3.037498 | 4.129756 | 0.250298 | 0.115695 | 3095.899453 | 61.918439 | 2.16342 | 0.000044 | 2.5883e6 | \[3\]: stats.plot() \[3\]: ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_19_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_19_0.png) --- # High-Frequency Grid Trading - Comparison Across Other Exchanges — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * High-Frequency Grid Trading - Comparison Across Other Exchanges * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.ipynb.txt) * * * High-Frequency Grid Trading - Comparison Across Other Exchanges[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html#High-Frequency-Grid-Trading---Comparison-Across-Other-Exchanges "Link to this heading") ===================================================================================================================================================================================================================================================================================================== So far, we have explored examples in Binance Futures. In this section, we demonstrate how results can vary for the same pair and parameter set across different exchanges, due to differences in order flow. Since each exchange may have its own distinct order flow, performance can differ significantly. This also highlights the need to explore alternative parameter sets to optimize performance for each specific exchange. By doing so, you can extend your analysis to other platforms such as OKX, Hyperliquid, and more. \[1\]: import json import datetime import itertools from multiprocessing import Pool import polars as pl import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from matplotlib import pyplot as plt from hftbacktest import BUY, SELL, GTX, LIMIT, BUY\_EVENT, SELL\_EVENT from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder from hftbacktest.stats import LinearAssetRecord @njit def gridtrading(hbt, recorder, relative\_half\_spread, relative\_grid\_interval, min\_grid\_step, grid\_num, skew, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size max\_position \= grid\_num \* order\_qty \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 normalized\_position \= position / order\_qty relative\_bid\_depth \= relative\_half\_spread + skew \* normalized\_position relative\_ask\_depth \= relative\_half\_spread \- skew \* normalized\_position \# Please see Market Making with Alpha example series. \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html # \# Without alpha, this relies heavily on rebates combined with short-term mean reversion to the current price — \# a behavior that has been observed to be particularly strong in altcoins. alpha \= 0.0 forecast\_mid\_price \= mid\_price + alpha \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(forecast\_mid\_price \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= np.maximum(forecast\_mid\_price \* (1.0 + relative\_ask\_depth), best\_ask) \# min\_grid\_step enforces grid interval changes to be no less than min\_grid\_step, which \# stabilizes the grid\_interval and keeps the orders on the grid more stable. grid\_interval \= max(np.round(forecast\_mid\_price \* relative\_grid\_interval / min\_grid\_step) \* min\_grid\_step, min\_grid\_step) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True Binance Futures[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html#Binance-Futures "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[3\]: dates \= \[\] date \= datetime.datetime(2025, 4, 1) until \= datetime.datetime(2025, 5, 19) while date <= until: dates.append(date.strftime("%Y%m%d")) date += datetime.timedelta(days\=1) latency\_data \= np.concatenate( \[np.load('binance\_latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in dates\] ) def backtest(args): asset\_name, asset\_info, half\_spread \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('binance\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 data \= \['binance\_data/{}/{}\_{}.npz'.format(asset\_name, asset\_name, date) for date in dates\] asset \= ( BacktestAsset() .data(data) .initial\_snapshot('binance\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] grid\_num \= 20 grid\_interval \= half\_spread skew \= half\_spread / grid\_num min\_grid\_step \= asset\_info\['tick\_size'\] recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, min\_grid\_step, grid\_num, skew, order\_qty) hbt.close() recorder.to\_npz('binance\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread)) \[4\]: %%capture with open('binance\_assets.json', 'r') as f: assets \= json.load(f) args \= list(itertools.product(list(assets.items()), \[0.0005, 0.0010, 0.0015\])) args \= \[(\*tup, x) for tup, x in args\] with Pool(16) as p: print(p.map(backtest, args)) As we have demonstrated so far, the strategy collectively produces a favorable equity curve when factoring in rebates. \[6\]: equity\_values \= {} half\_spread \= 0.001 for asset\_name, \_ in assets.items(): data \= np.load('binance\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[7\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[7\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_7_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_7_1.png) In addition, this is for demonstration purpose to use the single parameter set, but you can find more optimum parameter set for each pair, which also have a risk to lead to the overfitting. \[9\]: for half\_spread in \[0.0005, 0.001, 0.0015\]: data \= np.load('binance\_stats/gridtrading\_SUIUSDT\_{}.npz'.format(half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_0.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_1.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_2.png) \[10\]: for half\_spread in \[0.0005, 0.001, 0.0015\]: data \= np.load('binance\_stats/gridtrading\_ADAUSDT\_{}.npz'.format(half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_0.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_1.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_10_2.png) Bybit[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html#Bybit "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.0025% rebate, the market maker rebate available on Bybit Futures. See Introduction to the Market Maker Incentive Program for more details. \[12\]: latency\_data \= np.concatenate( \[np.load('bybit\_latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in dates\] ) def backtest(args): asset\_name, asset\_info, half\_spread \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 data \= \['bybit\_data/{}/{}\_{}.npz'.format(asset\_name, asset\_name, date) for date in dates\] asset \= ( BacktestAsset() .data(data) \# Tardis collects Bybit data from Tokyo, but the Bybit server is located in Singapore. # \# Therefore, if we assume our strategy will run in Singapore, we need to adjust for the feed latency. \# The round-trip time (RTT) between Tokyo and Singapore is approximately 70 ms. \# For our purposes, we subtract 30 ms as the estimated one-way latency from Singapore to Tokyo, including a small buffer. # \# https://docs.tardis.dev/historical-data-details/bybit#market-data-collection-details \# https://bybit-exchange.github.io/docs/faq#where-are-bybits-servers-located \# https://elitwilliams.medium.com/geographic-latency-in-crypto-how-to-optimally-co-locate-your-aws-trading-server-to-any-exchange-58965ea173a8 .latency\_offset(\-30\_000\_000) .initial\_snapshot('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.000025, 0.00055) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] grid\_num \= 20 grid\_interval \= half\_spread skew \= half\_spread / grid\_num min\_grid\_step \= asset\_info\['tick\_size'\] recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, min\_grid\_step, grid\_num, skew, order\_qty) hbt.close() recorder.to\_npz('bybit\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread)) \[13\]: %%capture with open('bybit\_assets.json', 'r') as f: assets \= json.load(f) args \= list(itertools.product(list(assets.items()), \[0.0005, 0.0010, 0.0015\])) args \= \[(\*tup, x) for tup, x in args\] with Pool(16) as p: print(p.map(backtest, args)) \[14\]: equity\_values \= {} half\_spread \= 0.001 for asset\_name, \_ in assets.items(): data \= np.load('bybit\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[15\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[15\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_15_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_15_1.png) You can observe performance differences across exchanges using the same parameter set. \[17\]: for half\_spread in \[0.0005, 0.001, 0.0015\]: data \= np.load('bybit\_stats/gridtrading\_SUIUSDT\_{}.npz'.format(half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_0.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_1.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_17_2.png) \[18\]: for half\_spread in \[0.0005, 0.001, 0.0015\]: data \= np.load('bybit\_stats/gridtrading\_ADAUSDT\_{}.npz'.format(half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_0.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_1.png) ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_18_2.png) Regarding applying the same parameter set to the multiple pairs in generalized way, you need more generalized model about the volatility and the order flow such like you’ve seen in the GLFT example. Also, you can see volatility regime change over time-horizon affecting the performance in April in the plot. We will provide the example another emprical approach other than the GLFT example, as a simplified version using non-parametric approach. --- # Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.ipynb.txt) * * * Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Gu%C3%A9ant%E2%80%93Lehalle%E2%80%93Fernandez-Tapia-Market-Making-Model-and-Grid-Trading "Link to this heading") ========================================================================================================================================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Overview "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------- Grid trading is straightforward and easy to comprehend, and it excels in high-frequency environments. However, given the intricacies of high-frequency trading, which necessitate comprehensive tick-by-tick simulation with latencies and order fill simulation, optimizing the ideal spread, order interval, and skew can be a challenging task. Furthermore, these values fluctuate over time, especially in response to market conditions, making a fixed setup less than optimal. To improve grid trading’s adaptability, one solution is to combine it with a well-developed market-making model. Let’s delve into how this can be achieved. Guéant–Lehalle–Fernandez-Tapia Market Making Model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Gu%C3%A9ant%E2%80%93Lehalle%E2%80%93Fernandez-Tapia-Market-Making-Model "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ This model represents an advanced evolution of the well-known Avellaneda-Stoikov model and provides a closed-form approximation of asymptotic behavior for terminal time T. Simply, this model does not specify a terminal time, which makes it suitable for typical stocks, spot assets, or crypto perpetual contracts. By employing this model, it is anticipated that the half spread and skew will be accurately adjusted according to market conditions. In this analysis, we will focus on equations (4.6) and (4.7) in [Optimal market making](https://arxiv.org/abs/1605.01862) and explore how they can be applied to real-world scenarios. The optimal bid quote depth, δapproxb∗, and ask quote depth, δapproxa∗, are derived from the fair price as follows: (4.6)δapproxb∗(q)\=1ξΔlog(1+ξΔk)+2q+Δ2γσ22AΔk(1+ξΔk)kξΔ+1(4.7)δapproxa∗(q)\=1ξΔlog(1+ξΔk)−2q−Δ2γσ22AΔk(1+ξΔk)kξΔ+1 Let’s introduce c1 and c2 and define them by extracting the volatility 𝜎 from the square root: c1\=1ξΔlog(1+ξΔk)c2\=γ2AΔk(1+ξΔk)kξΔ+1 Now we can rewrite equations (4.6) and (4.7) as follows: δapproxb∗(q)\=c1+Δ2σc2+qσc2δapproxa∗(q)\=c1+Δ2σc2−qσc2 As you can see, this consists of the half spread and skew. q represents a market maker’s inventory(position). half spread\=C1+Δ2σC2skew\=σC2δapproxb∗(q)\=half spread+skew×qδapproxa∗(q)\=half spread−skew×q Thus, bid price\=fair price−(half spread+skew×q)ask price\=fair price+(half spread−skew×q) You can find similarities in what the following two articles describe. [Stochastic Control Theory and High Frequency Trading](https://ieor.columbia.edu/files/seasdepts/industrial-engineering-operations-research/pdf-files/Borden_D_FESeminar_Sp10.pdf) [How to Market Make Bitcoin Derivatives Lesson 2](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-2/) Calculating Trading Intensity[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Calculating-Trading-Intensity "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To determine the optimal quotes, we need to compute c1 and c2. In order to do that, we need to calibrate A and k of trading intensity, as well as calculate the market volatility σ. Trading intensity is defined as: λ\=Aexp⁡(−kδ) We will calibrate these values using market data according to [the this article](https://quant.stackexchange.com/questions/36073/how-does-one-calibrate-lambda-in-a-avellaneda-stoikov-market-making-problem) . In order to do that, we need to record market order’s arrivals. Our market maker will react every 100ms, which means they will post or cancel orders at this interval. So, our quotes’ trading intensity will be measured in the same time-step. Ideally, we should also account for our orders’ queue position; however, to simplify the problem, we will not consider the order queue position in this analysis. \[1\]: from numba import njit from hftbacktest import BUY\_EVENT import numpy as np @njit def measure\_trading\_intensity\_and\_volatility(hbt): tick\_size \= hbt.depth(0).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(0): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(0) depth \= hbt.depth(0) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception return arrival\_depth\[:t\], mid\_price\_chg\[:t\] Since we’re not considering the order’s queue position when measuring trading intensity, only market trades that cross our quote will be counted as executed. **Note:** The trading intensity in `out` of `measure_trading_intensity` is incorrectly in half-tick units instead of tick units. Although this fix requires adjusting parameters in all related examples, the example is left unchanged to preserve existing results. \[2\]: @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] Run HftBacktest to replay the market and record order arrival depth and price changes. \[3\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) arrival\_depth, mid\_price\_chg \= measure\_trading\_intensity\_and\_volatility(hbt) \_ \= hbt.close() Measure trading intensity from the recorded order arrival depth and plot it. \[4\]: tmp \= np.zeros(500, np.float64) \# Measures trading intensity (lambda) for the first 10-minute window. lambda\_ \= measure\_trading\_intensity(arrival\_depth\[:6\_000\], tmp) \# Since it is measured for a 10-minute window, divide by 600 to convert it to per second. lambda\_ /= 600 \# Creates ticks from the mid-price. ticks \= np.arange(len(lambda\_)) + .5 \[5\]: from matplotlib import pyplot as plt plt.plot(ticks, lambda\_) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') \[5\]: Text(0, 0.5, 'Count (per second)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png) Calibrate A and k using linear regression, since by taking the logarithm of both sides of lambda, it becomes logλ\=−kδ+logA. \[6\]: @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept \[7\]: y \= np.log(lambda\_) k\_, logA \= linear\_regression(ticks, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=0.8426573649994981, k=0.016958811558646644 \[8\]: plt.plot(lambda\_) plt.plot(A \* np.exp(\-k \* ticks)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[8\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png) As you can see, the fitted lambda function is not accurate across the entire range. More specifically, it overestimates the trading intensity for the shallow range near the mid-price and underestimates it for the deep range away from the mid-price. Since our quotes are likely to be placed in the range close to the mid-price, at least under typical market conditions (excluding high volatility conditions), we will refit the function specifically for the nearest range. \[9\]: \# Refits for the range un to 70 ticks. x\_shallow \= ticks\[:70\] lambda\_shallow \= lambda\_\[:70\] y \= np.log(lambda\_shallow) k\_, logA \= linear\_regression(x\_shallow, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=2.986162360812285, k=0.04235741115084049 \[10\]: plt.plot(lambda\_shallow) plt.plot(A \* np.exp(\-k \* x\_shallow)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[10\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png) Now, we have a more accurate trading intensity function. Let’s see where our quote will be placed. But before we do that, let’s calculate the volatility first. \[11\]: \# Since we need volatility in ticks per square root of a second and our measurement is every 100ms, \# multiply by the square root of 10. volatility \= np.nanstd(mid\_price\_chg) \* np.sqrt(10) print(volatility) 10.725509539115974 Compute c1 and c2 according to the equations. \[12\]: @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 In the Guéant–Lehalle–Fernandez-Tapia formula, Δ\=1 and ξ\=γ. the value of γ is arbitrarily chosen. \[13\]: gamma \= 0.05 delta \= 1 volatility \= 10.69 c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= 1 \* c1 + 1 / 2 \* c2 \* volatility skew \= c2 \* volatility print('half\_spread\_tick={}, skew={}'.format(half\_spread\_tick, skew)) half\_spread\_tick=20.47208533844371, skew=9.76326865029227 What does it mean when your quote is positioned 20 ticks away from the mid-price? By analyzing the recorded order arrival depth, you can identify the number of market trades you’ll participate in as a market maker, measured in terms of count instead of volume. Additionally, the skew appears to be quite strong, as accumulating just two positions offsets the entire half spread. \[14\]: from scipy import stats \# inverse of percentile pct \= stats.percentileofscore(arrival\_depth\[np.isfinite(arrival\_depth)\], half\_spread\_tick) your\_pct \= 100 \- pct print('{:.2f}%'.format(your\_pct)) 1.86% Approximately 1.86% of market trades per given time-step could execute your quote. Be aware that it’s not the percentage of the traded quantity. Implement a Market Maker using the Model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Implement-a-Market-Maker-using-the-Model "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. In this example, we will disregard the forecast term and assume that the fair price is equal to the mid price, as we can expect the intrinsic value to remain stable in the short term. \[15\]: from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT out\_dtype \= np.dtype(\[\ ('half\_spread\_tick', 'f8'),\ ('skew', 'f8'),\ ('volatility', 'f8'),\ ('A', 'f8'),\ ('k', 'f8')\ \]) @njit def glft\_market\_maker(hbt, recorder): tick\_size \= hbt.depth(0).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.zeros(10\_000\_000, out\_dtype) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(0): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(0) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) position \= hbt.position(0) orders \= hbt.orders(0) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Cancel orders if they differ from the updated bid and ask prices. order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.price != bid\_price) or (order.side \== SELL and order.price != ask\_price) ): hbt.cancel(0, order.order\_id, False) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / tick\_size) if bid\_price\_as\_order\_id not in orders: hbt.submit\_buy\_order(0, bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX, LIMIT, False) if position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / tick\_size) if ask\_price\_as\_order\_id not in orders: hbt.submit\_sell\_order(0, ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t\].half\_spread\_tick \= half\_spread\_tick out\[t\].skew \= skew out\[t\].volatility \= volatility out\[t\].A \= A out\[t\].k \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[16\]: from hftbacktest import Recorder from hftbacktest.stats import LinearAssetRecord asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[16\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-03 23:59:50 | \-246.379582 | \-264.130529 | \-0.020574 | 0.020601 | 13579.57171 | 590.242857 | \-0.998715 | \-0.000035 | 19790.625 | \[17\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_31_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_31_0.png) \[18\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_32_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_32_0.png) Adjustment factors[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Adjustment-factors "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It looks like the skew is too strong, which is why the market maker is hesitant to take on the position. To alleviate the skew, you can introduce adjustment factors, adj1 and adj2, to the calculated half spread and skew, as follow. half spreadadj\=half spread×adj1skewadj\=skew×adj2 \[19\]: from numba.typed import Dict @njit def glft\_market\_maker(hbt, recorder): tick\_size \= hbt.depth(0).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.zeros(10\_000\_000, out\_dtype) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 \# Uses the same value as gamma. order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(0): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(0) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) position \= hbt.position(0) orders \= hbt.orders(0) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Cancel orders if they differ from the updated bid and ask prices. order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.price\_tick != bid\_price\_tick) or (order.side \== SELL and order.price\_tick != ask\_price\_tick) ): hbt.cancel(0, order.order\_id, False) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / tick\_size) if bid\_price\_as\_order\_id not in orders: hbt.submit\_buy\_order(0, bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX, LIMIT, False) if position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / tick\_size) if ask\_price\_as\_order\_id not in orders: hbt.submit\_sell\_order(0, ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t\].half\_spread\_tick \= half\_spread\_tick out\[t\].skew \= skew out\[t\].volatility \= volatility out\[t\].A \= A out\[t\].k \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[20\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[20\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-03 23:59:50 | 1.202048 | 1.471295 | 0.000359 | 0.004763 | 10987.271675 | 477.498424 | 0.075478 | 7.5295e-7 | 27563.655 | \[21\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_0.png) Improved, but even when accounting for rebates, it can only achieve breakeven at best. As shown below, both the half spread and skew move together, primarily influenced by the c2 and the market volatility. \[22\]: import polars as pl records \= recorder.get(0) df \= pl.DataFrame(out).with\_columns( pl.Series('timestamp', records\['timestamp'\]), pl.Series('price', records\['price'\]) ).with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns') ) df \= df.group\_by\_dynamic( 'timestamp', every\='5m' ).agg( pl.col('price').last(), pl.col('half\_spread\_tick').last(), pl.col('skew').last(), pl.col('volatility').last(), pl.col('A').last(), pl.col('k').last(), ) fig, (ax1, ax2) \= plt.subplots(2, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 6) ax1.plot(df\['timestamp'\], df\['half\_spread\_tick'\]) ax1.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax1.set\_ylabel('Half spread (tick)') ax2.plot(df\['timestamp'\], df\['skew'\]) ax2.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax2.set\_ylabel('Skew (tick)') \[22\]: Text(0, 0.5, 'Skew (tick)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png) \[23\]: fig, (ax1, ax2, ax3) \= plt.subplots(3, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 9) ax1.plot(df\['timestamp'\], df\['volatility'\]) ax1.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax1.set\_ylabel('Volatility ($ tick/s^{1/2} $)') ax2.plot(df\['timestamp'\], df\['A'\]) ax2.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax2.set\_ylabel('A ($ s^{-1} $)') ax3.plot(df\['timestamp'\], df\['k'\]) ax3.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax3.set\_ylabel('k ($ tick^{-1} $)') \[23\]: Text(0, 0.5, 'k ($ tick^{-1} $)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_39_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_39_1.png) In the 5-day backtest, it’s evident that profits are generated through rebates, as a result of maintaining high trading volume by consistently posting quotes. \[24\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[24\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 16.282366 | 20.682178 | 0.031145 | 0.009818 | 9463.81907 | 422.448163 | 3.172133 | 0.000015 | 34458.375 | \[25\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_42_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_42_0.png) Integrating Grid Trading[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Integrating-Grid-Trading "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Creating a grid from the bid and ask prices derived from the Guéant–Lehalle–Fernandez-Tapia market making model. \[26\]: from numba.typed import Dict from numba import uint64 @njit def gridtrading\_glft\_mm(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 order\_qty \= 1 max\_position \= 20 grid\_num \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[27\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= gridtrading\_glft\_mm(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[27\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 19.774661 | 24.630456 | 0.055856 | 0.007438 | 5878.736082 | 262.524795 | 7.509437 | 0.000043 | 30859.215 | \[28\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_46_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_46_0.png) You can see it works even better with other coins as well. In the next example, we will show how to create multiple markets to achieve better risk-adjusted returns. \[29\]: asset \= ( BacktestAsset() .data(\[\ 'data/ltcusdt\_20230701.npz',\ 'data/ltcusdt\_20230702.npz',\ 'data/ltcusdt\_20230703.npz',\ 'data/ltcusdt\_20230704.npz',\ 'data/ltcusdt\_20230705.npz'\ \]) .initial\_snapshot('data/ltcusdt\_20230630\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20230701.npz',\ 'latency/feed\_latency\_20230702.npz',\ 'latency/feed\_latency\_20230703.npz',\ 'latency/feed\_latency\_20230704.npz',\ 'latency/feed\_latency\_20230705.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(300.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= gridtrading\_glft\_mm(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=3000) stats.summary() \[29\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-07-01 00:00:00 | 2023-07-05 23:59:50 | 17.17992 | 23.062973 | 0.122535 | 0.032973 | 3425.879303 | 122.800909 | 3.716196 | 0.0002 | 2930.06 | \[30\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_49_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_49_0.png) Wrapping up[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Wrapping-up "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Thus far, we have illustrated how to apply the model to a real-world example. For a more effective market-making algorithm, consider dividing this model into the following categories: * Half-spread: As shown, the half-spread is a function of trading intensity and market volatility. An exponential function used for trading intensity might not be suitable for the entire range. You could develop a more refined approach to convert trading intensity to half-spread. Additionally, while historical trading intensity and market volatility are utilized here, you could forecast short-term trading intensity and volatility to respond more agilely to changes in market conditions. This might involve strategies that use news, events, liquidity vacuums, and other factors to predict volatility explosions. * Skew: The skew is also a function of trading intensity and market volatility. In this model, only inventory risk is considered, but you can also account for other risks, particularly when making multiple markets. BARRA is a good example of other risks that can be managed similarly. * Fair Value Pricing: In this model, the fair price is equal to the mid-price, however, you need to incorporate forecasts such as the micro-price and fair value pricing through correlated assets to enhance the strategy. * Hedging: Hedging is especially crucial when making multiple markets, as it serves as a valuable tool for managing risks. We will address a few more topics in upcoming examples. References[](https://hftbacktest.readthedocs.io/en/latest/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#References "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------- [Dealing with the Inventory Risk - A solution to the market making problem](https://arxiv.org/abs/1105.3115) [Optimal market making](https://arxiv.org/abs/1605.01862) Knight Capital Group [Stochastic Control Theory and High Frequency Trading](https://ieor.columbia.edu/files/seasdepts/industrial-engineering-operations-research/pdf-files/Borden_D_FESeminar_Sp10.pdf) BitMEX Market Making Series [Algo Trading & Market Making](https://blog.bitmex.com/wp-content/uploads/2019/11/Algo-Trading-and-Market-Making.pdf) [How to Market Make Bitcoin Derivatives Lesson 1](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-1/) [How to Market Make Bitcoin Derivatives Lesson 2](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-2/) \[ \]: --- # Market Making with Alpha - Basis — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Market Making with Alpha - Basis * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.ipynb.txt) * * * Market Making with Alpha - Basis[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html#Market-Making-with-Alpha---Basis "Link to this heading") ==================================================================================================================================================================================================== **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import datetime import os import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord import polars as pl Download L1 (book ticker) data to calculate the basis between spot and futures. \[3\]: start\_date \= datetime.datetime.strptime('20240901', '%Y%m%d') end\_date \= datetime.datetime.strptime('20241031', '%Y%m%d') tardis\_token \= "" \[4\]: def download\_from\_tardis(exchange, stream, symbol, start\_date, end\_date, output\_path, token): date \= start\_date while date <= end\_date: yyyymmdd \= date.strftime('%Y%m%d') year \= yyyymmdd\[:4\] month \= yyyymmdd\[4:6\] day \= yyyymmdd\[6:\] output\_file \= os.path.join(output\_path, f'{symbol}\_{yyyymmdd}.csv.gz') header \= f'"Authorization: Bearer {token}"' !wget \--header\={header} https://datasets.tardis.dev/v1/{exchange}/{stream}/{year}/{month}/{day}/{symbol}.csv.gz \-O {output\_file} date += datetime.timedelta(days\=1) \[5\]: download\_from\_tardis('binance', 'book\_ticker', 'BTCUSDT', start\_date, end\_date, 'spot/book\_ticker/BTCUSDT', tardis\_token) download\_from\_tardis('binance-futures', 'book\_ticker', 'BTCUSDT', start\_date, end\_date, 'usdm/book\_ticker/BTCUSDT', tardis\_token) Precompute the basis for faster backtesting. \[7\]: def load\_bookticker(file): return pl.read\_csv(file, schema\={ 'exchange': pl.String, 'symbol': pl.String, 'timestamp': pl.Int64, 'local\_timestamp': pl.Int64, 'ask\_amount': pl.Float64, 'ask\_price': pl.Float64, 'bid\_price': pl.Float64, 'bid\_amount': pl.Float64 }).with\_columns( pl.col('local\_timestamp').cast(pl.Datetime), mid\_price \= (.5 \* (pl.col('bid\_price') + pl.col('ask\_price'))), ).select(\['local\_timestamp', 'mid\_price'\]) def prepare\_px\_basis(spot\_file, futures\_file, sampling\_interval, rolling\_window): spot \= load\_bookticker(spot\_file) futures \= load\_bookticker(futures\_file) \# Resamples prices to calculate the basis. spot\_rs \= spot.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last() ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) futures\_rs \= futures.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last(), ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) return spot\_rs.join( futures\_rs, left\_on\='local\_timestamp', right\_on\='local\_timestamp', how\='full' ).with\_columns( rolling\_mean\_basis\=( pl.col('mid\_price\_right').forward\_fill() \- pl.col('mid\_price').forward\_fill() \# Computes the basis ).rolling\_mean(window\_size\=rolling\_window), \# Computes the moving average of the basis over the given window. ).select( local\_timestamp\=pl.col('local\_timestamp').dt.timestamp('ns'), spot\=pl.col('mid\_price'), basis\=pl.col('rolling\_mean\_basis') ) \[8\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_basis( f'spot/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000 \# 5-minute ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[9\]: np.savez\_compressed('px\_basis\_BTCUSDT\_5m', data\=precompute\_data) \[10\]: precompute\_data \= np.load('px\_basis\_BTCUSDT\_5m.npz')\['data'\] A market-making model based on the basis. Since the basis is often considered stationary, various time series analysis techniques, such as MA, AR, ARMA and etc, can be applied. Here, the simplest model, the Moving Average, is used for demonstration. This approach assumes that the basis will revert to the average of a given past period. \[12\]: @njit def basis\_mm( hbt, stat, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) data\_i \= 0 last\_spot \= np.nan last\_basis \= np.nan while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 #-------------------------------------------------------- \# Computes bid price and ask price. order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) normalized\_position \= position / order\_qty relative\_bid\_depth \= half\_spread + skew \* normalized\_position relative\_ask\_depth \= half\_spread \- skew \* normalized\_position \# Reads the latest observable spot price and basis from the precomputed data. while data\_i < len(precompute\_data): if precompute\_data\[data\_i, 0\] \> hbt.current\_timestamp: if data\_i \> 0: last\_spot \= precompute\_data\[data\_i \- 1, 1\] last\_basis \= precompute\_data\[data\_i \- 1, 2\] break data\_i += 1 \# Our fair price is calculated as the spot price + the rolling average of the basis fair\_px \= last\_spot + last\_basis bid\_price \= min(fair\_px \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= max(fair\_px \* (1.0 + relative\_ask\_depth), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[13\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size basis\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcusdt\_basis\_5m.npz') CPU times: user 1h 2min 41s, sys: 1min 45s, total: 1h 4min 27s Wall time: 40min 22s \[14\]: data \= np.load('stats/underlying\_btcusdt\_basis\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[14\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 3.280936 | 4.380048 | 0.05166 | 0.024406 | 537.702738 | 26.885072 | 2.116701 | 0.000032 | 1.0409e6 | \[15\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Basis_15_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Basis_15_0.png) On Binance, the BTCFDUSD spot market has a higher trading volume than the BTCUSDT spot market. BTCFDUSD records a daily trading volume of \\$3 billion, while BTCUSDT has \\$2.5 billion. Alternatively, you may consider using the exact index rather than a specific spot. You can find the weights composing the index using the API. [https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Composite-Index-Symbol-Information](https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Composite-Index-Symbol-Information) \[17\]: download\_from\_tardis('binance', 'book\_ticker', 'BTCFDUSD', start\_date, end\_date, 'spot/book\_ticker/BTCFDUSD', tardis\_token) \[18\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_basis( f'spot/book\_ticker/BTCFDUSD/BTCFDUSD\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000 \# 5-minute ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[19\]: np.savez\_compressed('px\_basis\_BTCFDUSD\_5m', data\=precompute\_data) \[20\]: precompute\_data \= np.load('px\_basis\_BTCFDUSD\_5m.npz')\['data'\] \[21\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size basis\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcfdusd\_basis\_5m.npz') CPU times: user 1h 5min 24s, sys: 1min 50s, total: 1h 7min 14s Wall time: 42min 59s \[22\]: data \= np.load('stats/underlying\_btcfdusd\_basis\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[22\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 2.069684 | 2.647596 | 0.045228 | 0.047641 | 479.043661 | 23.952189 | 0.949337 | 0.000031 | 1.0376e6 | \[23\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Basis_23_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_Basis_23_0.png) --- # Market Making with Alpha - APT — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Market Making with Alpha - APT * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Market%20Making%20with%20Alpha%20-%20APT.ipynb.txt) * * * Market Making with Alpha - APT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Market-Making-with-Alpha---APT "Link to this heading") ============================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Overview "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------- Continuing from [Market Making with Alpha - Basis](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html) , this example demonstrates market making based on [Arbitrage Pricing Theory](https://en.wikipedia.org/wiki/Arbitrage_pricing_theory) . **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import datetime import os import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord import polars as pl import statsmodels.api as sm from matplotlib import pyplot def load\_bookticker(file): return pl.read\_csv(file, schema\={ 'exchange': pl.String, 'symbol': pl.String, 'timestamp': pl.Int64, 'local\_timestamp': pl.Int64, 'ask\_amount': pl.Float64, 'ask\_price': pl.Float64, 'bid\_price': pl.Float64, 'bid\_amount': pl.Float64 }).with\_columns( pl.col('local\_timestamp').cast(pl.Datetime), mid\_price \= (.5 \* (pl.col('bid\_price') + pl.col('ask\_price'))), ).select(\['local\_timestamp', 'mid\_price'\]) def prepare\_px\_return(spot\_file, futures\_file, sampling\_interval, rolling\_window, shift): spot \= load\_bookticker(spot\_file) futures \= load\_bookticker(futures\_file) \# Resamples prices to calculate returns. spot\_rs \= spot.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last() ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) futures\_rs \= futures.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last(), ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) \# When computing returns, if one chooses the past price at a specific time point, \# it may result in selecting an noiser value, leading to a noisier return calculation. # \# To mitigate this issue, the average price over a past period is used. \# For example, to compute 5-minute returns, the average price over a 5-minute window centered around 5 minutes ago is used. return spot\_rs.join( futures\_rs, left\_on\='local\_timestamp', right\_on\='local\_timestamp', how\='full' ).with\_columns( futures\_px\=pl.col('mid\_price\_right').forward\_fill(), spot\_px\=pl.col('mid\_price').forward\_fill() ).with\_columns( futures\_past\_px\=pl.col('futures\_px').rolling\_mean(window\_size\=rolling\_window).shift(shift), spot\_past\_px\=pl.col('spot\_px').rolling\_mean(window\_size\=rolling\_window).shift(shift) ).with\_columns( local\_timestamp\=pl.col('local\_timestamp').dt.timestamp('ns'), spot\_return\=pl.col('spot\_px') / pl.col('spot\_past\_px') \- 1, futures\_return\=pl.col('futures\_px') / pl.col('futures\_past\_px') \- 1, ).select( \['local\_timestamp', 'spot\_return', 'spot\_past\_px', 'futures\_return', 'futures\_past\_px'\] ) \[2\]: start\_date \= datetime.datetime.strptime('20240901', '%Y%m%d') end\_date \= datetime.datetime.strptime('20241031', '%Y%m%d') \[3\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_return( f'spot/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000, \# 5-minute 1500 \# 2.5-minute, the average price over a 5-minute window centered around 5 minutes ago ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[4\]: np.savez\_compressed("precompute\_px\_return\_BTCUSDT\_5m", data\=precompute\_data) \[5\]: precompute\_data \= np.load("precompute\_px\_return\_BTCUSDT\_5m.npz")\["data"\] \[6\]: spot\_returns \= precompute\_data\[:, 1\] futures\_returns \= precompute\_data\[:, 3\] m \= np.isfinite(spot\_returns) & np.isfinite(futures\_returns) spot\_returns \= spot\_returns\[m\] futures\_returns \= futures\_returns\[m\] \[7\]: pyplot.scatter(spot\_returns, futures\_returns) pyplot.xlabel('Spot Returns') pyplot.ylabel('Futures Returns') pyplot.grid() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_7_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_7_0.png) Under Arbitrage Pricing Theory, the relationship between futures return and spot return can be expressed as: Returnfutures\=α+βspot∗Returnspot Under the assumption that βspot = 1 and α = 0, the futures return should be equal to the spot return. This also implies that any residual movement is mean-reverting to zero, similar to what is shown in the basis example. **Extending the Model** Beyond this basic relationship, additional return-contributing factors can be incorporated. For instance, returns from other exchanges’ Bitcoin markets, such as: * CME Bitcoin futures, Bybit’s BTC futures and other platforms’s BTC futures * Bitcoin ETFs * Spot prices from Coinbase, Kraken, and other platforms Moreover, this is not limited to the same asset. Other cryptocurrencies, traditional assets, and macroeconomic indices can be considered, such as: * Ethereum (ETH) * S&P 500 * Dollar Index Additionally, market microstructure factors, such as order book imbalance, can further enhance the model, as demonstrated in [our other example](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) . This broader framework allows for a more comprehensive understanding of price movements and their underlying drivers. \[10\]: @njit def apt\_mm( hbt, stat, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval\_, roi\_lb, roi\_ub ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) data\_i \= 0 spot\_return \= np.nan futures\_past\_px \= np.nan while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask while data\_i < len(precompute\_data): if precompute\_data\[data\_i, 0\] \> hbt.current\_timestamp: if data\_i \> 0: spot\_return \= precompute\_data\[data\_i \- 1, 1\] futures\_past\_px \= precompute\_data\[data\_i \- 1, 4\] break data\_i += 1 #-------------------------------------------------------- \# Computes bid price and ask price. mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) normalized\_position \= position / order\_qty relative\_bid\_depth \= half\_spread + skew \* normalized\_position relative\_ask\_depth \= half\_spread \- skew \* normalized\_position beta \= 1 alpha \= 0 return\_ \= beta \* spot\_return + alpha fair\_px \= (1 + return\_) \* futures\_past\_px bid\_price \= min(fair\_px \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= max(fair\_px \* (1.0 + relative\_ask\_depth), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size grid\_interval \= max(tick\_size, np.round(grid\_interval\_ \* fair\_px / tick\_size) \* tick\_size) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[11\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size apt\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcusdt\_return\_5m.npz') CPU times: user 1h 2min 18s, sys: 1min 45s, total: 1h 4min 3s Wall time: 40min 2s \[12\]: data \= np.load('stats/underlying\_btcusdt\_return\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() The history saving thread hit an unexpected error (OperationalError('database is locked')).History will not be written to the database. \[12\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 3.578158 | 4.821155 | 0.025127 | 0.010283 | 568.442193 | 11.368845 | 2.443528 | 0.000036 | 1.0415e6 | \[13\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_12_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_12_0.png) As demonstrated in the basis example, BTCFDUSD behaves in a similar manner. \[15\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_return( f'spot/book\_ticker/BTCFDUSD/BTCFDUSD\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000, \# 5-minute window 1500 \# 2.5-minute, the average price over a 5-minute window centered around 5 minutes ago ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[16\]: np.savez\_compressed("precompute\_px\_return\_BTCFDUSD\_5m", data\=precompute\_data) \[17\]: precompute\_data \= np.load("precompute\_px\_return\_BTCFDUSD\_5m.npz")\["data"\] \[18\]: spot\_returns \= precompute\_data\[:, 1\] futures\_returns \= precompute\_data\[:, 3\] m \= np.isfinite(spot\_returns) & np.isfinite(futures\_returns) spot\_returns \= spot\_returns\[m\] futures\_returns \= futures\_returns\[m\] \[19\]: pyplot.scatter(spot\_returns, futures\_returns) pyplot.xlabel('Spot Returns') pyplot.ylabel('Futures Returns') pyplot.grid() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_18_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_18_0.png) \[21\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size apt\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcfdusd\_return\_5m.npz') CPU times: user 1h 4min 56s, sys: 1min 52s, total: 1h 6min 48s Wall time: 42min 35s \[22\]: data \= np.load('stats/underlying\_btcfdusd\_return\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[22\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 3.125613 | 4.000555 | 0.031671 | 0.020096 | 504.372972 | 10.087474 | 1.575961 | 0.000051 | 1.2871e6 | \[23\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_21_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_21_0.png) Integrating Grid Trading[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Integrating-Grid-Trading "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[25\]: start\_date \= datetime.datetime.strptime('20250101', '%Y%m%d') end\_date \= datetime.datetime.strptime('20250228', '%Y%m%d') \[26\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_return( f'spot/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000, \# 5-minute 1500 \# 2.5-minute, the average price over a 5-minute window centered around 5 minutes ago ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[27\]: np.savez\_compressed("precompute\_px\_return\_BTCUSDT\_5m\_2025", data\=precompute\_data) \[28\]: precompute\_data \= np.load("precompute\_px\_return\_BTCUSDT\_5m\_2025.npz")\["data"\] \[29\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 5 grid\_interval \= 0.0003 \# a ratio relative to the fair price apt\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcusdt\_return\_5m\_2025.npz') CPU times: user 1h 31min 28s, sys: 2min 50s, total: 1h 34min 18s Wall time: 59min 57s \[30\]: data \= np.load('stats/underlying\_btcusdt\_return\_5m\_2025.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[30\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 1.509583 | 1.891524 | 0.023263 | 0.034334 | 546.794891 | 10.935656 | 0.677544 | 0.000036 | 1.2789e6 | \[31\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_29_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_29_0.png) \[32\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_return( f'spot/book\_ticker/BTCFDUSD/BTCFDUSD\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000, \# 5-minute 1500 \# 2.5-minute, the average price over a 5-minute window centered around 5 minutes ago ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[33\]: np.savez\_compressed("precompute\_px\_return\_BTCFDUSD\_5m\_202502", data\=precompute\_data) \[34\]: precompute\_data \= np.load('precompute\_px\_return\_BTCFDUSD\_5m\_2025.npz')\['data'\] \[35\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 5 grid\_interval \= 0.0003 \# a ratio relative to the fair price apt\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcfdusd\_return\_5m\_202502.npz') CPU times: user 1h 31min 38s, sys: 2min 56s, total: 1h 34min 34s Wall time: 1h 8s \[36\]: data \= np.load('stats/underlying\_btcfdusd\_return\_5m\_202502.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[36\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 2.43417 | 3.17886 | 0.045953 | 0.033234 | 506.877288 | 10.137426 | 1.382702 | 0.000077 | 1.3039e6 | \[37\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_35_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_35_0.png) Extension to the Multi-Factor Model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Extension-to-the-Multi-Factor-Model "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Simple Form: Utilizing Both BTCUSDT and BTCFDUSD Spot Returns[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Simple-Form:-Utilizing-Both-BTCUSDT-and-BTCFDUSD-Spot-Returns "Link to this heading") One of the simplest ways to incorporate both BTCUSDT and BTCFDUSD spot returns is by using equal beta, which takes the average of these two values. \[39\]: precompute\_data1 \= np.load('precompute\_px\_return\_BTCUSDT\_5m\_2025.npz')\['data'\] precompute\_data2 \= np.load('precompute\_px\_return\_BTCFDUSD\_5m\_2025.npz')\['data'\] \[40\]: df1 \= pl.DataFrame(precompute\_data1).filter( pl.col('column\_0').is\_not\_nan() ).with\_columns( pl.col('column\_0').cast(pl.Int64), ) df2 \= pl.DataFrame(precompute\_data2).filter( pl.col('column\_0').is\_not\_nan() ).with\_columns( pl.col('column\_0').cast(pl.Int64), ) \[41\]: precompute\_data \= df1.join( df2, left\_on\='column\_0', right\_on\='column\_0', how\='full' ).sort( 'column\_0' ).filter( pl.col('column\_0').is\_not\_null() ).select( local\_timestamp \= 'column\_0', btcusdt\_spot\_return \= 'column\_1', btcfdusd\_spot\_return \= 'column\_1\_right', futures\_past\_px \= 'column\_4', futures\_return \= 'column\_3' ).to\_numpy() \[42\]: @njit def apt\_multi\_mm( hbt, stat, half\_spread, skew, beta, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval\_, roi\_lb, roi\_ub ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) data\_i \= 0 spot\_return \= np.nan futures\_past\_px \= np.nan while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask while data\_i < len(precompute\_data): if precompute\_data\[data\_i, 0\] \> hbt.current\_timestamp: if data\_i \> 0: spot1\_return \= precompute\_data\[data\_i \- 1, 1\] spot2\_return \= precompute\_data\[data\_i \- 1, 2\] futures\_past\_px \= precompute\_data\[data\_i \- 1, 3\] break data\_i += 1 #-------------------------------------------------------- \# Computes bid price and ask price. mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) normalized\_position \= position / order\_qty relative\_bid\_depth \= half\_spread + skew \* normalized\_position relative\_ask\_depth \= half\_spread \- skew \* normalized\_position alpha \= 0 return\_ \= beta\[0\] \* spot1\_return + beta\[1\] \* spot2\_return + alpha fair\_px \= (1 + return\_) \* futures\_past\_px bid\_price \= min(fair\_px \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= max(fair\_px \* (1.0 + relative\_ask\_depth), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size grid\_interval \= max(tick\_size, np.round(grid\_interval\_ \* fair\_px / tick\_size) \* tick\_size) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[43\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 5 grid\_interval \= 0.0003 \# a ratio relative to the fair price beta \= np.asarray(\[0.5, 0.5\]) apt\_multi\_mm( hbt, recorder.recorder, half\_spread, skew, beta, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcspot\_return\_5m\_2025.npz') CPU times: user 1h 33min 2s, sys: 3min, total: 1h 36min 2s Wall time: 1h 1min 31s \[44\]: data \= np.load('stats/underlying\_btcspot\_return\_5m\_2025.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[44\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 2.266827 | 2.920951 | 0.038573 | 0.03424 | 489.961038 | 9.799065 | 1.126546 | 0.000067 | 1.3342e6 | \[45\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_43_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_43_0.png) ### MLR: Utilizing Both BTCUSDT and BTCFDUSD Spot Returns[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#MLR:-Utilizing-Both-BTCUSDT-and-BTCFDUSD-Spot-Returns "Link to this heading") Since these two variables are highly correlated, proper handling is necessary. One approach is the residual method, but other techniques, such as PCA, can also be used to eliminate correlation. Additionally, when applying Multiple Linear Regression, you may need to constrain beta values within a specific range, such as ensuring they remain positive. In such cases, more advanced techniques can be utilized. \[47\]: ts \= datetime.datetime(2025, 2, 1, tzinfo\=datetime.timezone.utc).timestamp() \* 1\_000\_000\_000 train\_end \= np.min(np.where(df\['local\_timestamp'\] \> ts)) \[48\]: precompute\_data \= df.filter( pl.col('btcusdt\_spot\_return').is\_not\_nan() & pl.col('btcfdusd\_spot\_return').is\_not\_nan() & pl.col('futures\_return').is\_not\_nan() ).to\_numpy() Given the assumption that the deviation in futures returns mean-reverts to the spot return, the target is set as the spot return. \[49\]: \# Regresses BTCUSDT spot returns on BTCFDUSD spot returns to get residuals. \# x1 = BTCFDUSD spot returns \# x2 = BTCUSDT spot returns x1 \= sm.add\_constant(precompute\_data\[:train\_end, 2\]) model\_x2 \= sm.OLS(precompute\_data\[:train\_end, 1\], x1).fit() x2\_residual \= precompute\_data\[:train\_end, 2\] \- model\_x2.predict(x1) \# Regresses BTCUSDT futures returns on BTCFDUSD spot returns and the residual of BTCUSDT spot returns. X \= sm.add\_constant(np.column\_stack((precompute\_data\[:train\_end, 2\], x2\_residual))) model \= sm.OLS(precompute\_data\[:train\_end, 4\], X).fit() print(model.summary()) OLS Regression Results ============================================================================== Dep. Variable: y R-squared: 0.997 Model: OLS Adj. R-squared: 0.997 Method: Least Squares F-statistic: 9.707e+09 Date: Sun, 09 Mar 2025 Prob (F-statistic): 0.00 Time: 10:52:52 Log-Likelihood: 2.1219e+08 No. Observations: 26644398 AIC: -4.244e+08 Df Residuals: 26644396 BIC: -4.244e+08 Df Model: 1 Covariance Type: nonrobust ============================================================================== coef std err t P>|t| \[0.025 0.975\] ------------------------------------------------------------------------------ const -2.297e-07 1.63e-08 -14.084 0.000 -2.62e-07 -1.98e-07 x1 1.0164 1.03e-05 9.85e+04 0.000 1.016 1.016 x2 -0.0117 1.19e-07 -9.85e+04 0.000 -0.012 -0.012 ============================================================================== Omnibus: 31540719.034 Durbin-Watson: 0.091 Prob(Omnibus): 0.000 Jarque-Bera (JB): 594213867106.353 Skew: -4.722 Prob(JB): 0.00 Kurtosis: 734.540 Cond. No. 1.12e+19 ============================================================================== Notes: \[1\] Standard Errors assume that the covariance matrix of the errors is correctly specified. \[2\] The smallest eigenvalue is 2.13e-31. This might indicate that there are strong multicollinearity problems or that the design matrix is singular. \[50\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 5 grid\_interval \= 0.0003 \# a ratio relative to the fair price beta \= np.asarray(\[\-0.0117, 1.0164\]) apt\_multi\_mm( hbt, recorder.recorder, half\_spread, skew, beta, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcspot2\_return\_5m\_2025.npz') CPU times: user 1h 29min 31s, sys: 3min 11s, total: 1h 32min 42s Wall time: 58min 22s \[51\]: data \= np.load('stats/underlying\_btcspot2\_return\_5m\_2025.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[51\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 3.066696 | 4.017417 | 0.057006 | 0.030539 | 533.455123 | 10.66894 | 1.86669 | 0.000091 | 1.3075e6 | \[52\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_APT_51_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Market_Making_with_Alpha_-_APT_51_0.png) A Comprehensive Framework for Pricing Models[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#A-Comprehensive-Framework-for-Pricing-Models "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let’s explore a more generalized approach to asset pricing from a conceptual standpoint. ### Core Market Drivers (Primary Price Movement)[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Core-Market-Drivers-(Primary-Price-Movement) "Link to this heading") The first component represents price movement driven by core market instruments — typically spot and futures markets across major venues. It can be expressed as: Return\_BTC = β00 \* BTCUSDT\_spot + β01 \* BTCFDUSD\_spot + β02 \* BTCUSDT\_futures\_on\_exchange1 + β03 \* BTCUSD\_inverse\_perpetual1 + β04 \* BTCUSD\_CME\_futures + β05 \* BTC\_ETF1 + ... This model doesn’t require all components. You can identify the most influential inputs using statistical methods (e.g., regression, PCA, or Granger causality), or by analyzing market depth, trading volume, and lead-lag relationships between exchanges. You can also see the importance of considering the Trad-Fi market — including CME futures, Bitcoin ETFs, and equity markets — by comparing weekday returns, which highlight a different return profile on weekends (typically better). ### Broader Market Influence (Cross-Asset Correlation)[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Broader-Market-Influence-(Cross-Asset-Correlation) "Link to this heading") Bitcoin’s price can also be influenced by the movement of other major cryptocurrencies — similar to how components interact with an index: Return\_CrossAsset = β10 \* ETHUSDT\_futures + β11 \* SOLUSDT\_futures + ... You may also use spot markets, but it’s important to select markets with high liquidity and trading volume, as they are more likely to drive broader price movements. ### Microstructure Signals & Alpha Factors[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Microstructure-Signals-&-Alpha-Factors "Link to this heading") Short-term price forecasts can benefit from market microstructure data and proprietary alpha signals. These might include: Return\_Alpha = β20 \* OrderBookImbalance1 + β21 \* OrderBookImbalance2 + β22 \* FundingRateAlpha + β23 \* OpenInterestAlpha + ... + β2n \* CustomAlpha\_n These signals are especially valuable for short-horizon trading, such as high-frequency or latency-sensitive strategies. ### Combined Pricing Model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Combined-Pricing-Model "Link to this heading") All components can be integrated into a single predictive return model: Forecast\_Return = β0 \* Return\_Self + Return\_BTC + Return\_CrossAsset + Return\_Alpha Return\_BTC and Return\_CrossAsset reflect structural or market-level influences and Return\_Alpha represents short-term, predictive signals based on microstructure or custom models. In addition, defining fair value price is crucial, as it shapes your trading setup. A straight defintion is to forecast future returns (e.g., 10s, 30s, 1min, 5min), depending on the trading horizon. The regression target should then be this fair value price. ### Exchange-Specific Application[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#Exchange-Specific-Application "Link to this heading") The effectiveness of this model may depend on your forecasting horizon: For medium-term forecasts (e.g., 1–5 minutes), this model can generalize across major venues such as Binance, Bybit, OKX, and Hyperliquid. For very short-term trading (e.g., sub-second to a few seconds), you need to account for exchange-specific dynamics such as latency, liquidity, and order flow patterns. For example, since Binance Futures has the highest trading volume, its price movements often lead the market. Other exchanges may lag behind. The simplest cross-exchange model might look like this: Return\_Bybit ≈ Return\_Binance This setup is useful for cross-exchange arbitrage or liquidity-driven strategies, where you exploit short-term dislocations between platforms. More examples incorporating additional factors beyond BTC returns and cross-exchange cases such as described in [https://hangukquant.github.io/scripts/market\_making](https://hangukquant.github.io/scripts/market_making) , will be added. --- # Order Latency Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Order Latency Data * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Order%20Latency%20Data.ipynb.txt) * * * Order Latency Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html#Order-Latency-Data "Link to this heading") ==================================================================================================================================================== To obtain more realistic backtesting results, accounting for latencies is crucial. Therefore, it’s important to collect both feed data and order data with timestamps to measure your order latency. The best approach is to gather your own order latencies. You can collect order latency based on your live trading or by regularly submitting orders at a price that cannot be filled and then canceling them for recording purposes. However, if you don’t have access to them or want to establish a target, you will need to artificially generate order latency. You can model this latency based on factors such as feed latency, trade volume, and the number of events. In this guide, we will demonstrate a simple method to generate order latency from feed latency using a multiplier and offset for adjustment. First, loads the feed data. \[1\]: import numpy as np data \= np.load('btcusdt\_20200201.npz')\['data'\] data \[1\]: array(\[(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\ ...,\ (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\ (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\ (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)\], dtype=\[('ev', '= 6\_000 \- 1: \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) notional\_position \= position \* mid\_price normalized\_position \= notional\_position / max\_notional\_position half\_spread\_tick \= volatility \* vol\_to\_half\_spread bid\_depth\_tick \= half\_spread\_tick \* (1 + skew \* normalized\_position) ask\_depth\_tick \= half\_spread\_tick \* (1 \- skew \* normalized\_position) \# Please see Market Making with Alpha example series. \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html # \# Without alpha, this relies heavily on rebates combined with short-term mean reversion to the current price — \# a behavior that has been observed to be particularly strong in altcoins. forecast\_mid\_price \= micro\_price \# mid\_price + b1 \* alpha \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / lot\_size), 1) \* lot\_size \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(forecast\_mid\_price \- bid\_depth\_tick \* tick\_size, best\_bid) ask\_price \= np.maximum(forecast\_mid\_price + ask\_depth\_tick \* tick\_size, best\_ask) \# min\_grid\_step enforces grid interval changes to be no less than min\_grid\_step, which \# stabilizes the grid\_interval and keeps the orders on the grid more stable. grid\_interval \= max(np.round(half\_spread\_tick \* tick\_size / min\_grid\_step) \* min\_grid\_step, min\_grid\_step) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if normalized\_position < 1 and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if normalized\_position \> \-1 and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) Binance[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html#Binance "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[2\]: dates \= \[\] date \= datetime.datetime(2025, 4, 1) until \= datetime.datetime(2025, 7, 31) while date <= until: dates.append(date.strftime("%Y%m%d")) date += datetime.timedelta(days\=1) \[3\]: latency\_data \= np.concatenate( \[np.load('latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in dates\] ) def backtest(args): asset\_name, asset\_info, vol\_to\_half\_spread \= args \# Obtains the mid-price of the assset to determine the range of interest for the market depth. snapshot \= np.load('binance\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 data \= \['binance\_data/{}/{}\_{}.npz'.format(asset\_name, asset\_name, date) for date in dates\] asset \= ( BacktestAsset() .data(data) .initial\_snapshot('binance\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 10) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) max\_notional\_position \= 1000 grid\_num \= 20 skew \= 1 min\_grid\_step \= asset\_info\['tick\_size'\] recorder \= Recorder(1, 300\_000\_000) gridtrading(hbt, recorder.recorder, vol\_to\_half\_spread, min\_grid\_step, grid\_num, skew, max\_notional\_position) hbt.close() recorder.to\_npz('gridtrading\_stats/binance\_{}\_{}.npz'.format(asset\_name, vol\_to\_half\_spread)) \[4\]: %%capture with open('binance\_assets.json', 'r') as f: assets \= json.load(f) args \= list(itertools.product(list(assets.items()), \[5\])) args \= \[(\*tup, x) for tup, x in args\] with Pool(4) as p: print(p.map(backtest, args)) \[5\]: data \= np.load('gridtrading\_stats/binance\_SOLUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[5\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_6_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_6_0.png) \[6\]: data \= np.load('gridtrading\_stats/binance\_ONDOUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[6\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_7_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_7_0.png) \[7\]: data \= np.load('gridtrading\_stats/binance\_XRPUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[7\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_8_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_8_0.png) \[8\]: equity\_values \= {} for i, (asset\_name, \_) in enumerate(assets.items()): data \= np.load('gridtrading\_stats/binance\_{}\_5.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[9\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[9\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_10_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_10_1.png) Bybit[](https://hftbacktest.readthedocs.io/en/latest/tutorials/High-Frequency%20Grid%20Trading%20-%20Simplified%20from%20GLFT.html#Bybit "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------ **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.0025% rebate, the market maker rebate available on Bybit Futures. See Introduction to the Market Maker Incentive Program for more details. \[10\]: dates \= \[\] date \= datetime.datetime(2025, 4, 1) until \= datetime.datetime(2025, 7, 31) while date <= until: dates.append(date.strftime("%Y%m%d")) date += datetime.timedelta(days\=1) \[11\]: latency\_data \= np.concatenate( \[np.load('bybit\_latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in dates\] ) def backtest(args): asset\_name, asset\_info, vol\_to\_half\_spread \= args \# Obtains the mid-price of the assset to determine the range of interest for the market depth. snapshot \= np.load('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 data \= \['bybit\_data/{}/{}\_{}.npz'.format(asset\_name, asset\_name, date) for date in dates\] asset \= ( BacktestAsset() .data(data) \# Tardis collects Bybit data from Tokyo, but the Bybit server is located in Singapore. # \# Therefore, if we assume our strategy will run in Singapore, we need to adjust for the feed latency. \# The round-trip time (RTT) between Tokyo and Singapore is approximately 70 ms. \# For our purposes, we subtract 30 ms as the estimated one-way latency from Singapore to Tokyo, including a small buffer. # \# https://docs.tardis.dev/historical-data-details/bybit#market-data-collection-details \# https://bybit-exchange.github.io/docs/faq#where-are-bybits-servers-located \# https://elitwilliams.medium.com/geographic-latency-in-crypto-how-to-optimally-co-locate-your-aws-trading-server-to-any-exchange-58965ea173a8 .latency\_offset(\-30\_000\_000) .initial\_snapshot('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.000025, 0.00055) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 10) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) max\_notional\_position \= 1000 grid\_num \= 20 skew \= 1 min\_grid\_step \= asset\_info\['tick\_size'\] recorder \= Recorder(1, 300\_000\_000) gridtrading(hbt, recorder.recorder, vol\_to\_half\_spread, min\_grid\_step, grid\_num, skew, max\_notional\_position) hbt.close() recorder.to\_npz('gridtrading\_stats/bybit\_{}\_{}.npz'.format(asset\_name, vol\_to\_half\_spread)) \[12\]: %%capture with open('bybit\_assets.json', 'r') as f: assets \= json.load(f) args \= list(itertools.product(list(assets.items()), \[5\])) args \= \[(\*tup, x) for tup, x in args\] with Pool(4) as p: print(p.map(backtest, args)) \[13\]: data \= np.load('gridtrading\_stats/bybit\_SOLUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[13\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_15_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_15_0.png) \[14\]: data \= np.load('gridtrading\_stats/bybit\_ONDOUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[14\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_16_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_16_0.png) \[15\]: data \= np.load('gridtrading\_stats/bybit\_XRPUSDT\_5.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() \[15\]: ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_17_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_17_0.png) \[16\]: equity\_values \= {} for i, (asset\_name, \_) in enumerate(assets.items()): data \= np.load('gridtrading\_stats/bybit\_{}\_5.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[17\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[17\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_19_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_High-Frequency_Grid_Trading_-_Simplified_from_GLFT_19_1.png) --- # Probability Queue Position Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Probability Queue Position Models * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Probability%20Queue%20Models.ipynb.txt) * * * Probability Queue Position Models[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Probability%20Queue%20Models.html#Probability-Queue-Position-Models "Link to this heading") ======================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Probability%20Queue%20Models.html#Overview "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------- Here, we will demonstrate how queue position models affect order fill simulation and, ultimately, the strategy’s performance. It is essential for accurate backtesting to find the proper queue position modeling by comparing backtest and real trading results. In this context, we will illustrate comparisons by changing queue position models. By doing this, you can determine the appropriate queue position model that aligns with the backtesting and real trading results. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[2\]: def backtest(args): asset\_name, asset\_info, model \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) if model \== 'SquareProbQueueModel': asset.power\_prob\_queue\_model(2) elif model \== 'LogProbQueueModel2': asset.log\_prob\_queue\_model2() elif model \== 'PowerProbQueueModel3': asset.power\_prob\_queue\_model3(3) else: raise ValueError hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name)) \[3\]: %%capture from multiprocessing import Pool import json with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'SquareProbQueueModel') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'LogProbQueueModel2') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'PowerProbQueueModel3') for k, v in assets.items()\])) \[4\]: import polars as pl from matplotlib import pyplot as plt def compute\_net\_equity(model): equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number \== 100: \# 5\_000 is capital for each trading asset. return net\_equity.with\_columns( (pl.col('equity') / asset\_number / 5\_000).alias('equity') ) np.seterr(divide\='ignore', invalid\='ignore') fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] for model in \['SquareProbQueueModel', 'LogProbQueueModel2', 'PowerProbQueueModel3'\]: net\_equity\_ \= compute\_net\_equity(model) pnl \= net\_equity\_\['equity'\].diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('100 assets, Daily SR={:.2f}, {}'.format(sr, model)) plt.plot(net\_equity\_\['timestamp'\], net\_equity\_\['equity'\] \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[4\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Probability_Queue_Models_4_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Probability_Queue_Models_4_1.png) --- # Queue-Based Market Making in Large Tick Size Assets — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Queue-Based Market Making in Large Tick Size Assets * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.ipynb.txt) * * * Queue-Based Market Making in Large Tick Size Assets[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Queue-Based-Market-Making-in-Large-Tick-Size-Assets "Link to this heading") ================================================================================================================================================================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Overview "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The significance of queue position is well-known in microstructure trading, particularly in assets with large tick sizes. This is because larger tick assets typically more constrained price movements. The impact of tick size is discussed in detail in [“Large tick assets: implicit spread and optimal tick size”](https://arxiv.org/pdf/1207.6325) . ![CRVUSDT_chart](https://github.com/nkaz001/hftbacktest/blob/master/docs/images/CRVUSDT_chart.png) **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Book Pressure[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Book-Pressure "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To begin, we will review the [Market Microstructure signals described in this article](https://blog.headlandstech.com/2017/08/) , which are similar to the concept of micro-price. Book imbalance is also addressed in [Market Making with Alpha - Order Book Imbalance](https://github.com/nkaz001/hftbacktest/blob/master/examples/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.ipynb) . \[1\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. \# If you set a lower value, the order will tend to stay to the best bid and offer, even when book pressure increases. \# You can think of it as a threshold for backing off based on book pressure. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= book\_pressure \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[2\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ f'data/CRVUSDT\_{date}.npz' for date in range(20240701, 20240732)\ \] + \[\ f'data/CRVUSDT\_{date}.npz' for date in range(20240801, 20240832)\ \]) .linear\_asset(1.0) .intp\_order\_latency(\[\ f'latency/amp\_feed\_latency\_{date}.npz' for date in range(20240701, 20240732)\ \] + \[\ f'latency/amp\_feed\_latency\_{date}.npz' for date in range(20240801, 20240832)\ \]) .power\_prob\_queue\_model(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.001) .lot\_size(0.1) .roi\_lb(0.0) .roi\_ub(2.0) .last\_trades\_capacity(1000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[3\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 14s, sys: 8.57 s, total: 8min 23s Wall time: 6min 49s \[4\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 16.386564 | 23.90151 | 2.848749 | 0.096359 | 106.774393 | 30.241524 | 29.563923 | 0.001519 | 2.4745 | \[5\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_5_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_5_0.png) Trade Impulse[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Trade-Impulse "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let’s examine how it changes when we incorporate the trade impulse. \[6\]: from hftbacktest import BUY\_EVENT @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 trade\_impulse\_adj \= 1.0 sum\_bbo\_qty \= np.empty(50\_000\_000, float64) i \= 0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) \# Computes the trade impulse last\_qty \= 0 if len(last\_trades) \> 0: if last\_trades\[\-1\].ev & BUY\_EVENT \== BUY\_EVENT: last\_qty \= last\_trades\[\-1\].qty else: last\_qty \= \-last\_trades\[\-1\].qty hbt.clear\_last\_trades(asset\_no) sum\_bbo\_qty\[i\] \= best\_bid\_qty + best\_ask\_qty i += 1 \# Uses the last 1-minute average BBO quantity as the denominator. trade\_impulse \= (tick\_size / 2.0) \* last\_qty / np.mean(sum\_bbo\_qty\[max(0, i \- 600):i\]) fair\_price \= book\_pressure + trade\_impulse \* trade\_impulse\_adj skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= fair\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[7\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[8\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 13s, sys: 8.03 s, total: 8min 21s Wall time: 6min 48s \[9\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[9\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 16.52588 | 24.122559 | 2.874579 | 0.096359 | 106.580844 | 30.186685 | 29.831983 | 0.001536 | 2.4745 | \[10\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_11_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_11_0.png) There is not much difference, as the last trade quantity is relatively small compared to the best bid and offer quantities. ![CRVUSDT_depth](https://github.com/nkaz001/hftbacktest/blob/master/docs/images/CRVUSDT_depth.png) The following example demonstrates a variant of trade impulse using aggregated trade quantities. \[11\]: @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 trade\_impulse\_adj \= 1.0 sum\_bbo\_qty \= np.empty(50\_000\_000, float64) i \= 0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) \# Computes the trading impulse last\_qty \= 0 for last\_trade in last\_trades: if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: last\_qty += last\_trade.qty else: last\_qty \-= \-last\_trade.qty hbt.clear\_last\_trades(asset\_no) sum\_bbo\_qty\[i\] \= best\_bid\_qty + best\_ask\_qty i += 1 \# Uses the last 1-minute average BBO quantity as the denominator. trade\_impulse \= (tick\_size / 2.0) \* last\_qty / np.mean(sum\_bbo\_qty\[max(0, i \- 600):i\]) fair\_price \= book\_pressure + trade\_impulse \* trade\_impulse\_adj skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= fair\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[12\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[13\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 13s, sys: 7.99 s, total: 8min 21s Wall time: 6min 48s \[14\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[14\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 13.831429 | 20.083391 | 2.503742 | 0.114379 | 113.35505 | 32.174754 | 21.889804 | 0.001255 | 2.828 | \[15\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_17_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_17_0.png) You can also adjust `trade_impulse_adj` to modify the impact of the trade impulse. Alternatively, you can explore other ways to compute the trade impulse, such as `(best_bid * best_ask_qty + best_ask * best_bid_qty + last_px * last_qty) / (best_bid_qty + best_ask_qty + last_qty)`, VWAP, etc. Pure Queue-Based Model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Pure-Queue-Based-Model "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- One possible reason for this strategy’s profitability is the limited price movement due to the large tick size. For instance, CRVUSDT has a tick size of 38 basis points (0.001 / 0.26 \* 10,000), which is comparatively very larger than BTCUSDT, where the tick size is approximately 0.018 basis points (0.1 / 54,000 \* 10,000). This also highlights the importance of queue position modeling in fill simulations for assets with large tick sizes. In the CRVUSDT charts shown above, observing the trading activities, you can see that most trades occur at the best bid and ask prices, with little change in the overall price level. This suggests an opportunity to adjust the microstructure signal into a purely queue-based signal. For example, if there is sufficient quantity to maintain the price level, preventing it from moving adversely, we can choose to maintain our quote. Let’s explore how this can be implemented in a simplified form. \[16\]: @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 qty\_threshold \= 250\_000 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty skew\_val \= skew \* normalized\_position if best\_bid\_qty < qty\_threshold and skew\_val \> 0: bid\_price \= best\_bid \- tick\_size else: bid\_price \= best\_bid if best\_ask\_qty < qty\_threshold and skew\_val < 0: ask\_price \= best\_ask + tick\_size else: ask\_price \= best\_ask \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[17\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[18\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 16s, sys: 8.52 s, total: 8min 24s Wall time: 6min 51s \[19\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[19\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 13.199337 | 18.042325 | 3.95075 | 0.18009 | 1840.60021 | 509.758968 | 21.937611 | 0.000125 | 3.6905 | \[20\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_24_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_24_0.png) You can also explore more sophisticated approaches, such as dynamically controlling the `qty_threshold` and integrating it with the skew value, for example, `qty_threshold * (1 ± skew_val)`, similar to how skew is applied to the price. In other words, in the previous example, the spread is set in terms of price, but you can set the spread in terms of queue such as the queue position, the queue behind the order, the total queue, etc. Additionally, instead of reacting at fixed intervals, it may be more effective to respond to each incoming feed. This allows for faster reactions when the quantity at the BBO decreases rapidly, helping to avoid adverse selection. You can test this approach using the `wait_next_feed` method. --- # Making Multiple Markets — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Making Multiple Markets * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Making%20Multiple%20Markets.ipynb.txt) * * * Making Multiple Markets[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html#Making-Multiple-Markets "Link to this heading") =================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html#Overview "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------- By diversifying your assets and constructing a market-making book, you can achieve improved risk-adjusted returns through the effects of diversification. In this example, we will demonstrate how the statistics of your market-making portfolio change as you increase the number of assets for which you create markets. To implement Grid Trading using the GLFT market-making model across multiple assets universally without needing to adjust parameters, a few modifications are required: Order quantities vary between assets due to differences in price, trading volume, and liquidity in the order book. To backtest all at once, you need to normalize your order quantities and make adjustments accordingly. In certain assets, market trades primarily take place at the best bid and offer levels. Since we only calculate our trading intensity when market trades match our quotes, you may not achieve adequate trading intensity to suit your trading intensity function in such cases. As a result, you’ll need to explore alternative methods to determine your half spread and skew based on order arrival depths or you need to increase your reaction interval to get more deeper order arrival depth but it leads you to react delayed especially in a fast-moving market. See how adj2 is determined to normalize different order quantities. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. **Note:** The trading intensity in `out` of `measure_trading_intensity` is incorrectly in half-tick units instead of tick units. Although this fix requires adjusting parameters in all related examples, the example is left unchanged to preserve existing results. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= np.divide(w \* sxy \- sx \* sy, w \* sx2 \- sx\*\*2) intercept \= np.divide(sy \- slope \* sx, w) return slope, intercept @njit def gridtrading\_glft\_mm(hbt, recorder, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) The order quantity is determined to be equivalent to a notional value of $100. \[2\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/feed\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_{}.npz'.format(asset\_name)) By utilizing multiprocessing, backtesting of multiple assets can be conducted simultaneously. \[3\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[4\]: import polars as pl from hftbacktest.stats import LinearAssetRecord equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity You can see the equity curve of individual assets and notice how combining multiple assets can lead to a smoother equity curve, thereby enhancing risk-adjusted returns. \[5\]: from matplotlib import pyplot as plt for i, asset\_name in enumerate(assets.keys()): plt.figure(i, figsize\=(10, 3)) plt.plot(equity\_values\[asset\_name\]\['timestamp'\], equity\_values\[asset\_name\]\['equity'\]) plt.grid() plt.title(asset\_name) plt.ylabel('Equity ($)') ![../_images/tutorials_Making_Multiple_Markets_8_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_0.png) ![../_images/tutorials_Making_Multiple_Markets_8_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_1.png) ![../_images/tutorials_Making_Multiple_Markets_8_2.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_2.png) ![../_images/tutorials_Making_Multiple_Markets_8_3.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_3.png) ![../_images/tutorials_Making_Multiple_Markets_8_4.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_4.png) ![../_images/tutorials_Making_Multiple_Markets_8_5.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_5.png) ![../_images/tutorials_Making_Multiple_Markets_8_6.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_6.png) ![../_images/tutorials_Making_Multiple_Markets_8_7.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_7.png) ![../_images/tutorials_Making_Multiple_Markets_8_8.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_8.png) ![../_images/tutorials_Making_Multiple_Markets_8_9.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_9.png) ![../_images/tutorials_Making_Multiple_Markets_8_10.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_10.png) ![../_images/tutorials_Making_Multiple_Markets_8_11.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_11.png) ![../_images/tutorials_Making_Multiple_Markets_8_12.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_12.png) ![../_images/tutorials_Making_Multiple_Markets_8_13.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_13.png) ![../_images/tutorials_Making_Multiple_Markets_8_14.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_8_14.png) This presents an equity curve based on the number of assets, which are altcoins excluding BTC and ETH. \[6\]: from matplotlib import pyplot as plt fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[6\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_10_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_10_1.png) Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html#Impact-of-Order-Latency "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------- When applying amplified feed latency, you can observe a decrease in performance due to the effects of latency. \[7\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/amp\_feed\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_lat1\_{}.npz'.format(asset\_name)) \[8\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[9\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_lat1\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[9\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_14_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_14_1.png) When actual historical order latency is applied, the performance may deteriorate further compared to when amplified feed latency is used. \[10\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_lat2\_{}.npz'.format(asset\_name)) \[11\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[12\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_lat2\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[12\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_18_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_18_1.png) Therefore, understanding your order latency is crucial to achieving more precise backtest results. This understanding underscores the importance of latency reduction for market makers or high-frequency traders. This is why crypto exchanges not only offer maker rebates but also provide low-latency infrastructure to eligible market makers. Simpler model[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html#Simpler-model "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------- So far, we only cover ξ\>0 case, but ξ\=0 case would be more simple and appropriate in practice especially in cryptocurrencies. Revisit the equations (4.6) and (4.7) in [Optimal market making](https://arxiv.org/abs/1605.01862) and explore how they can be applied to real-world scenarios. The optimal bid quote depth, δapproxb∗, and ask quote depth, δapproxa∗, are derived from the fair price as follows in the case of ξ\=0: (4.6)δapproxb∗(q)\=1k+2q+Δ2γσ2e2AΔk(4.7)δapproxa∗(q)\=1k−2q−Δ2γσ2e2AΔk Let’s introduce c1 and c2 and define them by extracting the volatility 𝜎 from the square root as same as before: c1\=1kc2\=γe2AΔk Now we can rewrite equations (4.6) and (4.7) as follows: δapproxb∗(q)\=c1+Δ2σc2+qσc2δapproxa∗(q)\=c1+Δ2σc2−qσc2 It’s more concise and only need to adjust γ and its effect is more straightforward. \[13\]: @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[14\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm1\_{}.npz'.format(asset\_name)) \[15\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[16\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm1\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 5\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[16\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_24_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_24_1.png) \[17\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.001 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm2\_{}.npz'.format(asset\_name)) \[18\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) You can observe a more straight line in the equity curve with higher γ, which induces greater skew. However, it also experiences more severe drawdowns in fast-moving markets. Additionally, because of the higher skew, profits are diminished as there’s a greater tendency to close the position. \[19\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm2\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 5\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[19\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_28_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_28_1.png) A Case for More Assets[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Making%20Multiple%20Markets.html#A-Case-for-More-Assets "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------- The more assets you make a market for, the better risk-adjusted return you achieve. This effect becomes dramatically evident. \[20\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm3\_{}.npz'.format(asset\_name)) \[21\]: %%capture with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[22\]: equity\_values \= {} sr\_values \= {} np.seterr(divide\='ignore', invalid\='ignore') for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm3\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number % 10 \== 0: \# 5\_000 is capital for each trading asset. net\_equity\_ \= net\_equity\['equity'\] / asset\_number / 5\_000 pnl \= net\_equity\_.diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('{} assets,Daily SR={:.2f}'.format(asset\_number, sr)) plt.plot(net\_equity\['timestamp'\], net\_equity\_ \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[22\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_32_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Making_Multiple_Markets_32_1.png) --- # Fusing Depth Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Fusing Depth Data * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Fusing%20Depth%20Data.ipynb.txt) * * * Fusing Depth Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Fusing-Depth-Data "Link to this heading") ================================================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Overview "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------- Most cryptocurrency exchanges do not deliver true tick-by-tick Level-2 data. Instead, they provide conflated feeds in which individual order-book updates are aggregated over short intervals. For example, Binance Futures’ `depth@0ms` stream is still aggregated: You can confirm that its best-bid-offer (BBO) values update less frequently than those in the `bookTicker` stream, which captures every BBO change. Other venues state similar limitations explicitly—Bybit, for instance, publishes the Level 1 data (BBO) every 10ms, the Level 50 data every 20ms, and the Level 200 data every 100ms. To generate accurate fill simulations and realistic backtesting results, you must therefore fuse multiple depth streams into a single feed that preserves the highest possible update frequency and granularity. Let’s see Binance Futures as our example. Data Preparation[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Data-Preparation "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: \# !wget https://datasets.tardis.dev/v1/binance-futures/trades/2025/05/01/BTCUSDT.csv.gz -O BTCUSDT\_trades\_20250501.csv.gz \# !wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2025/05/01/BTCUSDT.csv.gz -O BTCUSDT\_incremental\_book\_L2\_20250501.csv.gz \# !wget https://datasets.tardis.dev/v1/binance-futures/book\_ticker/2025/05/01/BTCUSDT.csv.gz -O BTCUSDT\_book\_ticker\_20250501.csv.gz \[2\]: from hftbacktest.data.utils import tardis tardis.convert( \[\ 'BTCUSDT\_trades\_20250501.csv.gz',\ 'BTCUSDT\_incremental\_book\_L2\_20250501.csv.gz'\ \], output\_filename\='BTCUSDT\_20250501.npz', buffer\_size\=1\_000\_000\_000, snapshot\_mode\='process' ) Reading BTCUSDT\_trades\_20250501.csv.gz Reading BTCUSDT\_incremental\_book\_L2\_20250501.csv.gz Correcting the latency Correcting the event order Saving to BTCUSDT\_20250501.npz \[2\]: array(\[(3758096386, 1746057600043000000, 1746057600046245000, 94125.2, 1.0000e-02, 0, 0, 0.),\ (3758096387, 1746057600072000000, 1746057601025373000, 93954.8, 0.0000e+00, 0, 0, 0.),\ (3758096388, 1746057600072000000, 1746057601025373000, 94125.1, 1.0798e+01, 0, 0, 0.),\ ...,\ (3758096385, 1746143999978000000, 1746143999980195000, 96406. , 1.5590e+00, 0, 0, 0.),\ (3758096385, 1746143999978000000, 1746143999980195000, 96411.2, 6.1000e-02, 0, 0, 0.),\ (3758096385, 1746143999978000000, 1746143999980195000, 96423.2, 1.0130e+01, 0, 0, 0.)\], shape=(106343798,), dtype={'names': \['ev', 'exch\_ts', 'local\_ts', 'px', 'qty', 'order\_id', 'ival', 'fval'\], 'formats': \['= len(l2\_bbo): raise Exception return l2\_bbo\[:t\] \[4\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 asset \= ( BacktestAsset() .data(\['BTCUSDT\_20250501.npz'\]) .linear\_asset(1.0) .constant\_order\_latency(0, 0) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) timeout \= 100\_000\_000 \# 100ms l2\_bbo \= record\_l2\_bbo( hbt, timeout ) \_ \= hbt.close() CPU times: user 40.5 s, sys: 4.34 s, total: 44.9 s Wall time: 43.4 s Comparing BBO updates: Level-2 ([depth@0ms](mailto:depth%400ms) ) Stream vs bookTicker Stream[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Comparing-BBO-updates:-Level-2-(depth@0ms)-Stream-vs-bookTicker-Stream "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The `bookTicker` stream delivers updates more often and leads the Level-2 feed by a small margin. \[5\]: import polars as pl from matplotlib import pyplot as plt \[6\]: df\_l2\_bbo \= pl.DataFrame(l2\_bbo) df\_l2\_bbo.columns \= \['Local Timestamp', 'Bid', 'Ask', 'Bid Qty', 'Ask Qty'\] df\_l2\_bbo \= df\_l2\_bbo.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ).filter( (pl.col('Local Timestamp') \> pl.lit('2025-05-01 14:36:03').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) & (pl.col('Local Timestamp') < pl.lit('2025-05-01 14:36:5').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) ) \[7\]: df\_book\_ticker \= pl.read\_csv('BTCUSDT\_book\_ticker\_20250501.csv.gz').with\_columns( pl.from\_epoch('local\_timestamp', time\_unit\='us') ).select( 'local\_timestamp', 'bid\_price', 'ask\_price', 'bid\_amount', 'ask\_amount' ).filter( (pl.col('local\_timestamp') \> pl.lit('2025-05-01 14:36:03').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) & (pl.col('local\_timestamp') < pl.lit('2025-05-01 14:36:5').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) ) \[8\]: plt.figure(figsize\=(20, 8)) plt.step(df\_l2\_bbo\['Local Timestamp'\], df\_l2\_bbo\['Bid'\], where\='post') plt.step(df\_book\_ticker\['local\_timestamp'\], df\_book\_ticker\['bid\_price'\], where\='post') plt.legend(\['depth@0ms best bid', 'bookTicker best bid'\]) plt.grid() ![../_images/tutorials_Fusing_Depth_Data_11_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_11_0.png) \[9\]: plt.figure(figsize\=(20, 8)) plt.step(df\_l2\_bbo\['Local Timestamp'\], df\_l2\_bbo\['Ask'\], where\='post') plt.step(df\_book\_ticker\['local\_timestamp'\], df\_book\_ticker\['ask\_price'\], where\='post') plt.legend(\['depth@0ms best ask', 'bookTicker best ask'\]) plt.grid() ![../_images/tutorials_Fusing_Depth_Data_12_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_12_0.png) You’ll notice that the `bookTicker` stream delivers updates far more frequently—especially when you factor in changes to both price and quantity. \[10\]: with pl.Config(tbl\_rows\=100): print(df\_l2\_bbo) shape: (39, 5) ┌───────────────────────────────┬─────────┬─────────┬─────────┬─────────┐ │ Local Timestamp ┆ Bid ┆ Ask ┆ Bid Qty ┆ Ask Qty │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ datetime\[ns\] ┆ f64 ┆ f64 ┆ f64 ┆ f64 │ ╞═══════════════════════════════╪═════════╪═════════╪═════════╪═════════╡ │ 2025-05-01 14:36:03.008176128 ┆ 96351.4 ┆ 96351.5 ┆ 6.344 ┆ 7.159 │ │ 2025-05-01 14:36:03.060811008 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 1.297 │ │ 2025-05-01 14:36:03.112276992 ┆ 96351.4 ┆ 96351.5 ┆ 6.528 ┆ 0.128 │ │ 2025-05-01 14:36:03.163234048 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.188 │ │ 2025-05-01 14:36:03.215074048 ┆ 96351.4 ┆ 96351.5 ┆ 6.584 ┆ 2.819 │ │ 2025-05-01 14:36:03.266274048 ┆ 96351.4 ┆ 96351.5 ┆ 6.399 ┆ 1.996 │ │ 2025-05-01 14:36:03.316154112 ┆ 96351.4 ┆ 96351.5 ┆ 6.399 ┆ 2.127 │ │ 2025-05-01 14:36:03.369733888 ┆ 96363.4 ┆ 96380.3 ┆ 1.12 ┆ 0.006 │ │ 2025-05-01 14:36:03.421906944 ┆ 96379.6 ┆ 96379.7 ┆ 0.438 ┆ 0.036 │ │ 2025-05-01 14:36:03.472250880 ┆ 96385.9 ┆ 96387.3 ┆ 0.876 ┆ 0.201 │ │ 2025-05-01 14:36:03.522233088 ┆ 96387.3 ┆ 96388.0 ┆ 0.029 ┆ 0.034 │ │ 2025-05-01 14:36:03.573129984 ┆ 96387.3 ┆ 96388.0 ┆ 0.446 ┆ 0.235 │ │ 2025-05-01 14:36:03.624218112 ┆ 96387.9 ┆ 96388.0 ┆ 1.641 ┆ 1.045 │ │ 2025-05-01 14:36:03.675269120 ┆ 96387.9 ┆ 96388.0 ┆ 8.122 ┆ 1.654 │ │ 2025-05-01 14:36:03.726845952 ┆ 96398.6 ┆ 96402.2 ┆ 0.311 ┆ 0.116 │ │ 2025-05-01 14:36:03.777195008 ┆ 96407.8 ┆ 96413.7 ┆ 0.46 ┆ 0.104 │ │ 2025-05-01 14:36:03.829007104 ┆ 96410.9 ┆ 96414.1 ┆ 0.36 ┆ 0.981 │ │ 2025-05-01 14:36:03.879574016 ┆ 96412.8 ┆ 96414.1 ┆ 0.291 ┆ 0.016 │ │ 2025-05-01 14:36:03.929277952 ┆ 96410.9 ┆ 96412.0 ┆ 0.852 ┆ 0.005 │ │ 2025-05-01 14:36:03.985562880 ┆ 96414.7 ┆ 96415.0 ┆ 0.07 ┆ 0.005 │ │ 2025-05-01 14:36:04.032508928 ┆ 96414.7 ┆ 96415.1 ┆ 5.581 ┆ 0.06 │ │ 2025-05-01 14:36:04.082749952 ┆ 96408.5 ┆ 96410.0 ┆ 0.388 ┆ 0.025 │ │ 2025-05-01 14:36:04.134117120 ┆ 96404.7 ┆ 96406.0 ┆ 0.002 ┆ 0.65 │ │ 2025-05-01 14:36:04.185359104 ┆ 96404.7 ┆ 96405.8 ┆ 0.002 ┆ 0.37 │ │ 2025-05-01 14:36:04.235608064 ┆ 96403.7 ┆ 96405.8 ┆ 0.02 ┆ 0.783 │ │ 2025-05-01 14:36:04.287187968 ┆ 96403.6 ┆ 96404.9 ┆ 0.249 ┆ 2.194 │ │ 2025-05-01 14:36:04.338068992 ┆ 96402.1 ┆ 96404.9 ┆ 0.042 ┆ 3.069 │ │ 2025-05-01 14:36:04.388464128 ┆ 96404.7 ┆ 96404.9 ┆ 0.13 ┆ 2.421 │ │ 2025-05-01 14:36:04.440344064 ┆ 96404.6 ┆ 96404.9 ┆ 0.435 ┆ 4.269 │ │ 2025-05-01 14:36:04.490669056 ┆ 96404.6 ┆ 96404.8 ┆ 0.603 ┆ 1.21 │ │ 2025-05-01 14:36:04.541093120 ┆ 96404.6 ┆ 96404.8 ┆ 0.839 ┆ 3.744 │ │ 2025-05-01 14:36:04.594004992 ┆ 96404.6 ┆ 96404.7 ┆ 0.841 ┆ 0.761 │ │ 2025-05-01 14:36:04.644099072 ┆ 96404.6 ┆ 96404.7 ┆ 0.961 ┆ 2.639 │ │ 2025-05-01 14:36:04.694644992 ┆ 96404.6 ┆ 96404.7 ┆ 5.782 ┆ 3.732 │ │ 2025-05-01 14:36:04.746102016 ┆ 96404.6 ┆ 96404.7 ┆ 6.503 ┆ 6.072 │ │ 2025-05-01 14:36:04.797575936 ┆ 96404.6 ┆ 96404.7 ┆ 6.541 ┆ 6.28 │ │ 2025-05-01 14:36:04.848253952 ┆ 96404.6 ┆ 96404.7 ┆ 5.084 ┆ 7.323 │ │ 2025-05-01 14:36:04.898958080 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.868 │ │ 2025-05-01 14:36:04.951334912 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.284 │ └───────────────────────────────┴─────────┴─────────┴─────────┴─────────┘ \[11\]: with pl.Config(tbl\_rows\=100): print(df\_book\_ticker) shape: (1\_432, 5) ┌────────────────────────────┬───────────┬───────────┬────────────┬────────────┐ │ local\_timestamp ┆ bid\_price ┆ ask\_price ┆ bid\_amount ┆ ask\_amount │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ datetime\[μs\] ┆ f64 ┆ f64 ┆ f64 ┆ f64 │ ╞════════════════════════════╪═══════════╪═══════════╪════════════╪════════════╡ │ 2025-05-01 14:36:03.027063 ┆ 96351.4 ┆ 96351.5 ┆ 6.338 ┆ 7.157 │ │ 2025-05-01 14:36:03.027070 ┆ 96351.4 ┆ 96351.5 ┆ 6.338 ┆ 7.154 │ │ 2025-05-01 14:36:03.027072 ┆ 96351.4 ┆ 96351.5 ┆ 6.338 ┆ 7.121 │ │ 2025-05-01 14:36:03.029315 ┆ 96351.4 ┆ 96351.5 ┆ 6.35 ┆ 7.121 │ │ 2025-05-01 14:36:03.030317 ┆ 96351.4 ┆ 96351.5 ┆ 6.338 ┆ 7.121 │ │ 2025-05-01 14:36:03.045954 ┆ 96351.4 ┆ 96351.5 ┆ 6.338 ┆ 7.119 │ │ 2025-05-01 14:36:03.049547 ┆ 96351.4 ┆ 96351.5 ┆ 6.348 ┆ 7.119 │ │ 2025-05-01 14:36:03.050625 ┆ 96351.4 ┆ 96351.5 ┆ 6.348 ┆ 4.174 │ │ 2025-05-01 14:36:03.050627 ┆ 96351.4 ┆ 96351.5 ┆ 6.348 ┆ 2.452 │ │ 2025-05-01 14:36:03.050627 ┆ 96351.4 ┆ 96351.5 ┆ 6.348 ┆ 2.395 │ │ 2025-05-01 14:36:03.051616 ┆ 96351.4 ┆ 96351.5 ┆ 6.348 ┆ 1.764 │ │ 2025-05-01 14:36:03.052630 ┆ 96351.4 ┆ 96351.5 ┆ 6.358 ┆ 1.764 │ │ 2025-05-01 14:36:03.052633 ┆ 96351.4 ┆ 96351.5 ┆ 6.358 ┆ 1.762 │ │ 2025-05-01 14:36:03.052634 ┆ 96351.4 ┆ 96351.5 ┆ 6.358 ┆ 1.764 │ │ 2025-05-01 14:36:03.053796 ┆ 96351.4 ┆ 96351.5 ┆ 6.36 ┆ 1.764 │ │ 2025-05-01 14:36:03.053797 ┆ 96351.4 ┆ 96351.5 ┆ 6.36 ┆ 1.762 │ │ 2025-05-01 14:36:03.054626 ┆ 96351.4 ┆ 96351.5 ┆ 6.354 ┆ 1.762 │ │ 2025-05-01 14:36:03.054629 ┆ 96351.4 ┆ 96351.5 ┆ 6.354 ┆ 1.737 │ │ 2025-05-01 14:36:03.054634 ┆ 96351.4 ┆ 96351.5 ┆ 6.354 ┆ 1.349 │ │ 2025-05-01 14:36:03.055714 ┆ 96351.4 ┆ 96351.5 ┆ 6.364 ┆ 1.349 │ │ 2025-05-01 14:36:03.056808 ┆ 96351.4 ┆ 96351.5 ┆ 6.364 ┆ 1.297 │ │ 2025-05-01 14:36:03.059917 ┆ 96351.4 ┆ 96351.5 ┆ 6.366 ┆ 1.297 │ │ 2025-05-01 14:36:03.059917 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 1.297 │ │ 2025-05-01 14:36:03.064652 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 1.201 │ │ 2025-05-01 14:36:03.064653 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 1.087 │ │ 2025-05-01 14:36:03.064656 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 1.038 │ │ 2025-05-01 14:36:03.064660 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 0.937 │ │ 2025-05-01 14:36:03.064661 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 0.679 │ │ 2025-05-01 14:36:03.064663 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 0.592 │ │ 2025-05-01 14:36:03.067917 ┆ 96351.4 ┆ 96351.5 ┆ 6.368 ┆ 0.702 │ │ 2025-05-01 14:36:03.069890 ┆ 96351.4 ┆ 96351.5 ┆ 6.498 ┆ 0.702 │ │ 2025-05-01 14:36:03.071890 ┆ 96351.4 ┆ 96351.5 ┆ 6.492 ┆ 0.702 │ │ 2025-05-01 14:36:03.071894 ┆ 96351.4 ┆ 96351.5 ┆ 6.492 ┆ 0.592 │ │ 2025-05-01 14:36:03.071897 ┆ 96351.4 ┆ 96351.5 ┆ 6.498 ┆ 0.592 │ │ 2025-05-01 14:36:03.074200 ┆ 96351.4 ┆ 96351.5 ┆ 6.498 ┆ 0.702 │ │ 2025-05-01 14:36:03.078112 ┆ 96351.4 ┆ 96351.5 ┆ 6.508 ┆ 0.702 │ │ 2025-05-01 14:36:03.092415 ┆ 96351.4 ┆ 96351.5 ┆ 6.508 ┆ 0.592 │ │ 2025-05-01 14:36:03.095210 ┆ 96351.4 ┆ 96351.5 ┆ 6.858 ┆ 0.592 │ │ 2025-05-01 14:36:03.097268 ┆ 96351.4 ┆ 96351.5 ┆ 6.868 ┆ 0.592 │ │ 2025-05-01 14:36:03.097274 ┆ 96351.4 ┆ 96351.5 ┆ 6.862 ┆ 0.592 │ │ 2025-05-01 14:36:03.097277 ┆ 96351.4 ┆ 96351.5 ┆ 6.868 ┆ 0.592 │ │ 2025-05-01 14:36:03.098526 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.592 │ │ 2025-05-01 14:36:03.099564 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.385 │ │ 2025-05-01 14:36:03.099566 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.178 │ │ 2025-05-01 14:36:03.102809 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.147 │ │ 2025-05-01 14:36:03.102809 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.145 │ │ 2025-05-01 14:36:03.102840 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.124 │ │ 2025-05-01 14:36:03.104977 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.126 │ │ 2025-05-01 14:36:03.105552 ┆ 96351.4 ┆ 96351.5 ┆ 6.878 ┆ 0.128 │ │ 2025-05-01 14:36:03.108741 ┆ 96351.4 ┆ 96351.5 ┆ 6.528 ┆ 0.128 │ │ … ┆ … ┆ … ┆ … ┆ … │ │ 2025-05-01 14:36:04.888094 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 5.431 │ │ 2025-05-01 14:36:04.888317 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 5.051 │ │ 2025-05-01 14:36:04.888318 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.335 │ │ 2025-05-01 14:36:04.888320 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.323 │ │ 2025-05-01 14:36:04.888773 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.517 │ │ 2025-05-01 14:36:04.888778 ┆ 96404.6 ┆ 96404.7 ┆ 5.117 ┆ 3.517 │ │ 2025-05-01 14:36:04.888779 ┆ 96404.6 ┆ 96404.7 ┆ 5.117 ┆ 3.567 │ │ 2025-05-01 14:36:04.890093 ┆ 96404.6 ┆ 96404.7 ┆ 5.117 ┆ 3.217 │ │ 2025-05-01 14:36:04.890110 ┆ 96404.6 ┆ 96404.7 ┆ 5.117 ┆ 3.248 │ │ 2025-05-01 14:36:04.890111 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.248 │ │ 2025-05-01 14:36:04.890749 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.25 │ │ 2025-05-01 14:36:04.890756 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.253 │ │ 2025-05-01 14:36:04.890757 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.703 │ │ 2025-05-01 14:36:04.890758 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.701 │ │ 2025-05-01 14:36:04.893901 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 3.703 │ │ 2025-05-01 14:36:04.894774 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.208 │ │ 2025-05-01 14:36:04.894778 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.207 │ │ 2025-05-01 14:36:04.896090 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.208 │ │ 2025-05-01 14:36:04.898233 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.868 │ │ 2025-05-01 14:36:04.900924 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 5.218 │ │ 2025-05-01 14:36:04.900929 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.868 │ │ 2025-05-01 14:36:04.900930 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.867 │ │ 2025-05-01 14:36:04.902011 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.868 │ │ 2025-05-01 14:36:04.917421 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.934 │ │ 2025-05-01 14:36:04.919464 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.933 │ │ 2025-05-01 14:36:04.919471 ┆ 96404.6 ┆ 96404.7 ┆ 5.107 ┆ 4.934 │ │ 2025-05-01 14:36:04.931723 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.934 │ │ 2025-05-01 14:36:04.946773 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.284 │ │ 2025-05-01 14:36:04.946775 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.286 │ │ 2025-05-01 14:36:04.946779 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.809 │ │ 2025-05-01 14:36:04.948089 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.459 │ │ 2025-05-01 14:36:04.948091 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.936 │ │ 2025-05-01 14:36:04.948114 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.935 │ │ 2025-05-01 14:36:04.949397 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.933 │ │ 2025-05-01 14:36:04.949402 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.934 │ │ 2025-05-01 14:36:04.949405 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.932 │ │ 2025-05-01 14:36:04.949408 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.282 │ │ 2025-05-01 14:36:04.950159 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.284 │ │ 2025-05-01 14:36:04.951337 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.934 │ │ 2025-05-01 14:36:04.952587 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 4.932 │ │ 2025-05-01 14:36:04.954760 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.302 │ │ 2025-05-01 14:36:04.955915 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.301 │ │ 2025-05-01 14:36:04.956673 ┆ 96404.6 ┆ 96404.7 ┆ 5.001 ┆ 5.302 │ │ 2025-05-01 14:36:04.960766 ┆ 96404.6 ┆ 96404.7 ┆ 5.003 ┆ 5.302 │ │ 2025-05-01 14:36:04.970154 ┆ 96404.6 ┆ 96404.7 ┆ 5.003 ┆ 5.3 │ │ 2025-05-01 14:36:04.978932 ┆ 96404.6 ┆ 96404.7 ┆ 5.109 ┆ 5.3 │ │ 2025-05-01 14:36:04.981128 ┆ 96404.6 ┆ 96404.7 ┆ 5.109 ┆ 5.297 │ │ 2025-05-01 14:36:04.981130 ┆ 96404.6 ┆ 96404.7 ┆ 5.103 ┆ 5.297 │ │ 2025-05-01 14:36:04.981131 ┆ 96404.6 ┆ 96404.7 ┆ 5.103 ┆ 5.295 │ │ 2025-05-01 14:36:04.981142 ┆ 96404.6 ┆ 96404.7 ┆ 5.109 ┆ 5.295 │ └────────────────────────────┴───────────┴───────────┴────────────┴────────────┘ Fusing Multiple Depth Feeds[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Fusing-Multiple-Depth-Feeds "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------- To obtain the most frequent and fine-grained market depth updates, it is necessary to combine the depth feed with the book ticker feed. The `` `FuseMarketDepth `` <[https://hftbacktest.readthedocs.io/en/latest/reference/data\_utilities.html#hftbacktest.binding.FuseMarketDepth](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth) \>\`\_\_ utility helps fuse multiple depth update streams into a single order book view. HftBacktest includes a fused converter function `` `convert_fuse `` <[https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert\_fuse](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_fuse) \>\`\_\_ for Tardis data. **Note: Handling Timestamp Inconsistencies** When fusing multiple depth feeds, it’s possible that an event with a later exchange timestamp may be received before an event with an earlier timestamp from another feed. Accurately reconstructing the order book in such cases would require building a separate order book for each feed and then combining them. However, this utility builds only a single consolidated order book, where: * Updates are processed in order of local receipt time. * The price-level information are updated based on the exchange timestamp. If an older exchange-timestamped update arrives after a newer one for the same price level, it is discarded. This approach may lead to slight discrepancies between the local order book state and the actual state on the exchange. **Note: Order Book Inconsistency Between Feeds** Each depth feed may present a different snapshot of the order book. When combining them, inconsistencies may arise even if the fused result reflects more up-to-date information. For example: * A depth feed may show a best bid/ask (BBO) at 10/11. * A more current book ticker feed may show BBO at 10/14. When fused: The resulting book shows BBO at 10/14. But price levels above 14 (e.g., 15 and higher) still reflect outdated data from the original depth feed. This results in a partially updated and inconsistent order book. To maintain consistency with the depth feed’s original intent, you would need to build and maintain a separate order book for each feed. \[12\]: from hftbacktest.data.utils.tardis import convert\_fuse convert\_fuse( 'BTCUSDT\_trades\_20250501.csv.gz', 'BTCUSDT\_incremental\_book\_L2\_20250501.csv.gz', 'BTCUSDT\_book\_ticker\_20250501.csv.gz', tick\_size\=0.1, lot\_size\=0.001, output\_filename\='BTCUSDT\_fused\_20250501.npz' ) Correcting the latency Correcting the event order Saving to BTCUSDT\_fused\_20250501.npz \[12\]: array(\[(3489660929, 1746057600036000000, 1746057600038318000, 94125.2, 9.7830e+00, 0, 0, 0.),\ (3758096385, 1746057600036000000, 1746057600038318000, 94125.1, 1.0882e+01, 0, 0, 0.),\ (2684354562, 1746057600043000000, 1746057600046245000, 94125.2, 1.0000e-02, 0, 0, 0.),\ ...,\ (3758096385, 1746143999978000000, 1746143999980195000, 96423.2, 1.0130e+01, 0, 0, 0.),\ (3758096385, 1746143999979000000, 1746143999982227000, 96423.2, 9.8140e+00, 0, 0, 0.),\ (3758096385, 1746143999982000000, 1746143999985629000, 96423.2, 9.8170e+00, 0, 0, 0.)\], shape=(123281421,), dtype={'names': \['ev', 'exch\_ts', 'local\_ts', 'px', 'qty', 'order\_id', 'ival', 'fval'\], 'formats': \[' pl.lit('2025-05-01 14:36:03').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) & (pl.col('Local Timestamp') < pl.lit('2025-05-01 14:36:5').str.strptime(pl.Datetime, '%Y-%m-%d %H:%M:%S')) ) \[15\]: plt.figure(figsize\=(20, 8)) plt.step(df\_l2\_bbo\['Local Timestamp'\], df\_l2\_bbo\['Bid'\], where\='post') plt.step(df\_book\_ticker\['local\_timestamp'\], df\_book\_ticker\['bid\_price'\], where\='post') plt.step(df\_l2\_bbo\_fused\['Local Timestamp'\], df\_l2\_bbo\_fused\['Bid'\], where\='post') plt.legend(\['depth@0ms best bid', 'bookTicker best bid', 'fused best bid'\]) plt.grid() ![../_images/tutorials_Fusing_Depth_Data_20_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_20_0.png) \[16\]: plt.figure(figsize\=(20, 8)) plt.step(df\_l2\_bbo\['Local Timestamp'\], df\_l2\_bbo\['Ask'\], where\='post') plt.step(df\_book\_ticker\['local\_timestamp'\], df\_book\_ticker\['ask\_price'\], where\='post') plt.step(df\_l2\_bbo\_fused\['Local Timestamp'\], df\_l2\_bbo\_fused\['Ask'\], where\='post') plt.legend(\['depth@0ms best ask', 'bookTicker best ask', 'fused best ask'\]) plt.grid() ![../_images/tutorials_Fusing_Depth_Data_21_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_21_0.png) Backtest Results Comparison[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Backtest-Results-Comparison "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------- We now compare backtesting results between fused and non-fused data. \[17\]: from numba import uint64 from numba.typed import Dict from hftbacktest import ( GTX, LIMIT, BUY, SELL, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def basic\_mm( hbt, stat, half\_spread, skew, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) orders \= hbt.orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 mid\_price\_tick \= (depth.best\_bid\_tick + depth.best\_ask\_tick) / 2.0 #-------------------------------------------------------- \# Computes bid price and ask price. order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) normalized\_position \= position / order\_qty relative\_bid\_depth \= half\_spread + skew \* normalized\_position relative\_ask\_depth \= half\_spread \- skew \* normalized\_position bid\_price \= min(mid\_price \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= max(mid\_price \* (1.0 + relative\_ask\_depth), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[18\]: roi\_lb \= 50000 roi\_ub \= 150000 half\_spread \= 0.0005 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= 1\_000\_000 grid\_num \= 1 grid\_interval \= 0.1 asset \= ( BacktestAsset() .data(\['BTCUSDT\_nonfused\_20250501.npz'\]) .linear\_asset(1.0) .intp\_order\_latency(\['../latency/order\_latency\_20250501.npz'\]) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) basic\_mm( hbt, recorder.recorder, half\_spread, skew, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval ) \_ \= hbt.close() ### Backtesting with Non-Fused Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Backtesting-with-Non-Fused-Data "Link to this heading") \[19\]: data \= recorder.get(0) stats \= ( LinearAssetRecord(data) .resample('1s') .stats(book\_size\=1\_000\_000) ) stats.summary() \[19\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-05-01 00:00:00 | 2025-05-01 23:59:59 | 11.17529 | 15.92313 | 0.001077 | 0.00115 | 128.001481 | 6.4003 | 0.936022 | 0.000168 | 402046.58425 | \[20\]: stats.plot() \[20\]: ![../_images/tutorials_Fusing_Depth_Data_27_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_27_0.png) \[21\]: asset \= ( BacktestAsset() .data(\['BTCUSDT\_fused\_20250501.npz'\]) .linear\_asset(1.0) .intp\_order\_latency(\['../latency/order\_latency\_20250501.npz'\]) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) basic\_mm( hbt, recorder.recorder, half\_spread, skew, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval ) \_ \= hbt.close() ### Backtesting with Fused Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Backtesting-with-Fused-Data "Link to this heading") \[22\]: data \= recorder.get(0) stats \= ( LinearAssetRecord(data) .resample('1s') .stats(book\_size\=1\_000\_000) ) stats.summary() \[22\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-05-01 00:00:00 | 2025-05-01 23:59:59 | 10.345893 | 14.320701 | 0.001011 | 0.001167 | 119.001377 | 5.950202 | 0.865768 | 0.00017 | 301079.3632 | \[23\]: stats.plot() \[23\]: ![../_images/tutorials_Fusing_Depth_Data_31_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Fusing_Depth_Data_31_0.png) You may notice slight differences in order fills, which lead to position discrepancies and, ultimately, equity differences — particularly between 03:00 and 15:00. These order fill differences are closely tied to the order placement behavior, which depends on the characteristics of the strategy. The differences in the BBO as shown above can result in significant equity divergence during backtesting. **Note:** Some tutorial—especially older ones—were not backtested with fused market depth. Wrapping up[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Fusing%20Depth%20Data.html#Wrapping-up "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------- Since it uses more frequent data feeds, the backtesting process takes longer. There is always a trade-off between accuracy and speed in backtesting—there is no one-size-fits-all solution. Relaxing certain conditions, such as order queue position and latency modeling, can significantly speed up the process. Not all strategies require precise modeling of these factors, especially when dealing with small tick sizes and highly volatile assets like BTCUSDT. Please see [the next tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) about accelerated backtesting. --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ========================================================================================================================================================================================================================================================================================================================= For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Accelerated Backtesting — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Accelerated Backtesting * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Accelerated%20Backtesting.ipynb.txt) * * * Accelerated Backtesting[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Accelerated-Backtesting "Link to this heading") ================================================================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- There is a trade-off between the accuracy and speed of backtesting. _hftbacktest_ provides highly accurate results, but it is relatively slow, making it challenging to quickly test new ideas or optimize parameters through rapid iteration. To improve backtest speed, this approach excludes certain conditions, sacrificing a certain degree of accuracy. The main performance gain comes from precomputing fill conditions for a given running interval. This includes ignoring both fills in the queue position and the order response latency, enabling backtesting within a single loop iteration. By removing queue position estimation, we eliminate the need to fully replay the market depth feed or process every depth update. Thus, this approach accounts for feed latency and order entry latency, but not order-response latency. In the fill simulation, queue position is not modeled, so partial fills do not occur—orders are either fully filled when crossed or not filled at all. While these simplifications can result in a loss of accuracy—particularly in case that queue position fills are critical typically due to large tick sizes—they offer substantial performance gains. Fill Conditions[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Fill-Conditions "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------- * A **buy** order is eligible to fill when **order price >= best ask** or **order price > sell trade price**. * A **sell** order is eligible to fill when **order price <= best bid** or **order price < buy trade price**. Because queue position is not considered, equality does not count: * buy price == sell trade price → **not filled** * sell price == buy trade price → **not filled** In short, if the market price crosses the order price (strictly), the order is considered filled. Limit Order Fill Prices[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Limit-Order-Fill-Prices "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------- For a given time interval \[t\_i, t\_{i+1}\]: **Bid fill price (applies to buy orders):** bid fill price = min( lowest best ask at the exchange over the interval, (lowest sell trade price + one tick) at the exchange over the interval ) An open buy order with **order price >= bid fill price** is considered filled in the interval. **Ask fill price (applies to sell orders):** ask fill price = max( highest best bid at the exchange over the interval, (highest buy trade price - one tick) at the exchange over the interval ) An open sell order with **order price <= ask fill price** is considered filled in the interval. Order Response Latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Order-Response-Latency "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------- To maintain a single state (no separate local and exchange state) and utilize a single-loop iteration, this don’t account for order response latency. Consequently, all state changes—order acceptance, cancellation, fills, and position updates—are reflected immediately on the local side. Preprocessed Data Structure[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Preprocessed-Data-Structure "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- row\[t\] row\[t+1\] Local +-------------------------------------------------------------+---------------------------- |local\_ts\[t\] |local\_ts\[t+1\] | | |best\_bid\[t\] |best\_bid\[t+1\] |best\_ask\[t\] |best\_ask\[t+1\] +-------------------------------------------------------------+--------------------------- Exchange +-------------------------------------------------------------+--------------------------- | bid\_fill\[t+1\]| | ask\_fill\[t+1\]| +----------------------------+--------------------------------+--------------------------- | order entry latency at |order\_ack\_ts\[t\] | | local\_ts\[t\] | | | |best\_bid\_ack\[t\] | | |best\_ask\_ack\[t\] | | | | | bid\_fill\_ack\[t\]| bid\_fill\_after\_ack\[t\]| | ask\_fill\_ack\[t\]| ask\_fill\_after\_ack\[t\]| +----------------------------+--------------------------------+--------------------------- The open order which is already acknowledged by the exchange between local\_ts\[t\] to local\_ts\[t + 1\] is filled by bid\_fill\[t + 1\] or ask\_fill\[t + 1\] based on the fill condition we describe above and the local knows it at local\_ts\[t + 1\]. If a user sends a new order at local\_ts\[t\], this order is checked if accepted based on best\_bid\_ack\[t\] or best\_ask\_ack\[t\] (if it is GTX, or marketable order). And if the limit order is accepted, it is checked if is filled until local\_ts\[t + 1\] by bid\_fill\_after\_ack\[t\] or ask\_fill\_after\_ack\[t\]. A user know it at local\_ts\[t + 1\]. If a user sends a cancel order at local\_ts\[t\], the open order is checked if it is filled before cancel request is acknowledge by bid\_fill\_ack\[t\] or ask\_fill\_ack\[t\]. And also a user know it at local\_ts\[t + 1\]. If order entry latency is high enough that the order request arrives at exchange after local\_ts\[t + 1\], compose the row as shown in the following. Local +------------------------------+-------------------------------------------------------------+------------- |local\_ts\[t\] |local\_ts\[t+1\] |local\_ts\[t+2\] | | | |best\_bid\[t\] |best\_bid\[t+1\] | |best\_ask\[t\] |best\_ask\[t+1\] | +------------------------------+-------------------------------------------------------------+------------- Exchange +------------------------------+-------------------------------------------------------------+------------- | bid\_fill\[t+1\]| bid\_fill\[t+2\]| | ask\_fill\[t+1\]| ask\_fill\[t+2\]| +------------------------------+------------------------+------------------------------------+------------- | order entry latency at local\_ts\[t\] |order\_ack\_ts\[t\] | | | | | |best\_bid\_ack\[t\] | | |best\_ask\_ack\[t\] | | | | | bid\_fill\_ack\[t\]| bid\_fill\_after\_ack\[t\]| | ask\_fill\_ack\[t\]| ask\_fill\_after\_ack\[t\]| +------------------------------+------------------------+---+--------------------------------+------------- | | |order\_ack\_ts\[t+1\] | | | | | | | |best\_bid\_ack\[t+1\] | | | |best\_ask\_ack\[t+1\] | +------------------------------+----------------------------+--------------------------------+------------- | | order entry latency at |order\_ack\_ts\[t+1\] | | | local\_ts\[t+1\] | | | | |best\_bid\_ack\[t+1\] | | | |best\_ask\_ack\[t+1\] | | | | | | | bid\_fill\_ack\[t+1\]| bid\_fill\_after\_ack\[t+1\]| | | ask\_fill\_ack\[t+1\]| ask\_fill\_after\_ack\[t+1\]| +------------------------------+----------------------------+--------------------------------+------------- Preprocessing Market Data for Accelerated Backtesting[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Preprocessing-Market-Data-for-Accelerated-Backtesting "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: import numpy as np import numba as nb from numba import njit from numba.experimental import jitclass INVALID\_MIN \= 0 INVALID\_MAX \= np.iinfo(np.int64).max \- 1 \[5\]: @jitclass class Clock: timestamp: nb.int64\[:\] rn: nb.int64 ts: nb.int64 def \_\_init\_\_(self, timestamp, rn): self.timestamp \= timestamp self.rn \= rn if self.rn \>= len(self.timestamp): self.ts \= INVALID\_MAX else: self.ts \= self.timestamp\[self.rn\] def next(self): if self.rn \== len(self.timestamp) \- 1: self.ts \= INVALID\_MAX else: self.rn += 1 self.ts \= self.timestamp\[self.rn\] @njit def select\_event(timestamps): \# Finds the earliest timestamped event to process first. earliest\_ts \= INVALID\_MAX ev \= \-1 for i in range(len(timestamps)): if timestamps\[i\] < earliest\_ts: earliest\_ts \= timestamps\[i\] ev \= i return ev \[6\]: order\_latency\_dtype \= np.dtype(\[('req\_ts', 'i8'), ('exch\_ts', 'i8'), ('resp\_ts', 'i8'), ('\_padding', 'i8')\]) @jitclass class IntpOrderLatency: rn: nb.int64 data: nb.from\_dtype(order\_latency\_dtype)\[:\] def \_\_init\_\_(self, data): self.rn \= 0 self.data \= data def entry(self, timestamp): \# Returns the order entry latency, interpolated from the order entry latency before the timestamp \# and the order entry latency after the timestamp. while ( self.rn < len(self.data) and self.data\[self.rn\].req\_ts < timestamp ): self.rn += 1 if self.rn \== 0: entry\_latency \= self.data\[self.rn\].exch\_ts \- self.data\[self.rn\].req\_ts elif self.rn \== len(self.data): entry\_latency \= self.data\[self.rn \- 1\].exch\_ts \- self.data\[self.rn \- 1\].req\_ts else: \# todo: Handle negative latency values, which indicate a technical issue \# (e.g., server overload) that causes the order request to be rejected. \# Please see https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html prev\_req\_ts \= self.data\[self.rn \- 1\].req\_ts next\_req\_ts \= self.data\[self.rn\].req\_ts prev\_entry\_latency \= self.data\[self.rn \- 1\].exch\_ts \- prev\_req\_ts next\_entry\_latency \= self.data\[self.rn\].exch\_ts \- next\_req\_ts entry\_latency \= np.divide( next\_entry\_latency \- prev\_entry\_latency, next\_req\_ts \- prev\_req\_ts ) \* (timestamp \- prev\_req\_ts) + prev\_entry\_latency return entry\_latency \[7\]: @njit def ack\_order( tick\_size, order\_ack\_ts, next\_local\_ts, book\_ticker\_rn, book\_ticker\_exch\_ts, book\_ticker, trades\_rn, trades\_exch\_ts, trades ): \# This function finds the fill prices around order\_ack\_ts, as well as the best bid and best ask at order\_ack\_ts. \# - Fill prices between local\_ts\[t\] and order\_ack\_ts\[t\] \# - Fill prices between order\_ack\_ts\[t\] and local\_ts\[t + n\], where local\_ts\[t + n\] > order\_ack\_ts\[t\] \# Initializes the values from the last best bid and best ask. best\_bid\_tick \= round(book\_ticker\[book\_ticker\_rn \- 1\].bid\_px / tick\_size) ask\_fill\_tick \= ask\_fill\_tick\_ack \= best\_bid\_tick\_ack \= high\_best\_bid\_tick \= best\_bid\_tick best\_ask\_tick \= round(book\_ticker\[book\_ticker\_rn \- 1\].ask\_px / tick\_size) bid\_fill\_tick \= bid\_fill\_tick\_ack \= best\_ask\_tick\_ack \= low\_best\_ask\_tick \= best\_ask\_tick high\_buy\_tick \= INVALID\_MIN low\_sell\_tick \= INVALID\_MAX book\_ticker\_exch\_clock \= Clock(book\_ticker\_exch\_ts, book\_ticker\_rn) trades\_exch\_clock \= Clock(trades\_exch\_ts, trades\_rn) while True: ev \= select\_event(np.asarray(\[\ book\_ticker\_exch\_clock.ts,\ trades\_exch\_clock.ts,\ order\_ack\_ts,\ next\_local\_ts\ \])) if ev \== \-1: raise ValueError elif ev \== 0: best\_bid\_tick \= round(book\_ticker\[book\_ticker\_exch\_clock.rn\].bid\_px / tick\_size) best\_ask\_tick \= round(book\_ticker\[book\_ticker\_exch\_clock.rn\].ask\_px / tick\_size) if best\_bid\_tick \> high\_best\_bid\_tick: high\_best\_bid\_tick \= best\_bid\_tick if best\_ask\_tick < low\_best\_ask\_tick: low\_best\_ask\_tick \= best\_ask\_tick book\_ticker\_exch\_clock.next() elif ev \== 1: side \= trades\[trades\_exch\_clock.rn\].side px\_tick \= round(trades\[trades\_exch\_clock.rn\].px / tick\_size) if side \== 1 and px\_tick \> high\_buy\_tick: high\_buy\_tick \= px\_tick elif side \== \-1 and px\_tick < low\_sell\_tick: low\_sell\_tick \= px\_tick trades\_exch\_clock.next() elif ev \== 2: \# An order request is acknowledged by the exchange at order\_ack\_ts\[t\]. bid\_fill\_tick\_ack \= min(low\_sell\_tick + 1, low\_best\_ask\_tick) ask\_fill\_tick\_ack \= max(high\_buy\_tick \- 1, high\_best\_bid\_tick) best\_bid\_tick\_ack \= best\_bid\_tick best\_ask\_tick\_ack \= best\_ask\_tick high\_buy\_tick \= INVALID\_MIN high\_best\_bid\_tick \= best\_bid\_tick low\_sell\_tick \= INVALID\_MAX low\_best\_ask\_tick \= best\_ask\_tick order\_ack\_ts \= INVALID\_MAX elif ev \== 3: \# at local\_ts\[t + n\] > order\_ack\_ts\[t\] bid\_fill\_tick \= min(low\_sell\_tick + 1, low\_best\_ask\_tick) ask\_fill\_tick \= max(high\_buy\_tick \- 1, high\_best\_bid\_tick) break return ( bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick, ask\_fill\_tick ) \[8\]: @njit def preprocess\_data( tick\_size, end\_ts, local\_ts, book\_ticker\_exch\_ts, book\_ticker\_local\_ts, book\_ticker, trades\_exch\_ts, trades, order\_latency ): \# Preprocessed data \# All prices are in ticks to avoid additional operations to prevent floating-point comparison errors. out\_t \= 0 out\_size \= len(local\_ts) \# timestamp at local := local\_ts\[t\] out\_local\_ts \= np.empty(out\_size, np.int64) \# best bid at local at local\_ts\[t\] out\_best\_bid\_tick \= np.empty(out\_size, np.int64) \# best ask at local at local\_ts\[t\] out\_best\_ask\_tick \= np.empty(out\_size, np.int64) \# bid fill price in ticks for the interval local\_ts\[t - 1\] ~ local\_ts\[t\] \# any open buy orders during this interval with a price greater than or equal to this price are considered filled. out\_bid\_fill\_tick \= np.empty(out\_size, np.int64) \# ask fill price in ticks for the interval local\_ts\[t - 1\] ~ local\_ts\[t\] \# any open sell orders during this interval with a price less than or equal to this price are considered filled. out\_ask\_fill\_tick \= np.empty(out\_size, np.int64) \# order acknowledgment timestamp at the exchange, when an order is sent at local\_ts\[t\], is defined as order\_ack\_ts\[t\]. out\_order\_ack\_ts \= np.empty(out\_size, np.int64) \# bid fill price in ticks for the interval local\_ts\[t\] ~ order\_ack\_ts\[t\] \# any open buy orders during this interval with a price greater than or equal to this price are considered filled. out\_bid\_fill\_tick\_ack \= np.empty(out\_size, np.int64) \# ask fill price in ticks for the interval local\_ts\[t\] ~ order\_ack\_ts\[t\] \# any open sell orders during this interval with a price less than or equal to this price are considered filled. out\_ask\_fill\_tick\_ack \= np.empty(out\_size, np.int64) \# best bid at the exchange at order\_ack\_ts\[t\] \# used to determine whether the order should be accepted (limit or market) or rejected (GTX). out\_best\_bid\_tick\_ack \= np.empty(out\_size, np.int64) \# best ask at the exchange at order\_ack\_ts\[t\] \# used to determine whether the order should be accepted (limit or market) or rejected (GTX). out\_best\_ask\_tick\_ack \= np.empty(out\_size, np.int64) \# bid fill price in ticks for the interval order\_ack\_ts\[t\] ~ local\_ts\[t + n\] where local\_ts\[t + n\] > order\_ack\_ts\[t\]. \# any open buy orders during this interval with a price greater than or equal to this price are considered filled. out\_bid\_fill\_tick\_after\_ack \= np.empty(out\_size, np.int64) \# ask fill price in ticks for the interval order\_ack\_ts\[t\] ~ local\_ts\[t + n\] where local\_ts\[t + n\] > order\_ack\_ts\[t\]. \# any open sell orders during this interval with a price less than or equal to this price are considered filled. out\_ask\_fill\_tick\_after\_ack \= np.empty(out\_size, np.int64) local\_best\_bid\_tick \= exch\_best\_bid\_tick \= INVALID\_MIN local\_best\_ask\_tick \= exch\_best\_ask\_tick \= INVALID\_MAX high\_buy\_tick \= high\_best\_bid\_tick \= INVALID\_MIN low\_sell\_tick \= low\_best\_ask\_tick \= INVALID\_MAX \# Initializes the clocks \# todo: For better accuracy, it also needs to combine the best bid and ask from Level-2 market depth data \# with the best bid and ask from the book ticker. book\_ticker\_exch\_clock \= Clock(book\_ticker\_exch\_ts, 0) book\_ticker\_local\_clock \= Clock(book\_ticker\_local\_ts, 0) trades\_exch\_clock \= Clock(trades\_exch\_ts, 0) local\_clock \= Clock(local\_ts, 0) order\_latency\_rn \= 0 while local\_clock.ts <= end\_ts: \# Selects the event to process. ev \= select\_event(np.asarray(\[\ book\_ticker\_exch\_clock.ts,\ book\_ticker\_local\_clock.ts,\ trades\_exch\_clock.ts,\ local\_clock.ts\ \])) if ev \== \-1: \# Should not happen. raise ValueError elif ev \== 0: \# Updates the current exchange best bid and best ask. exch\_best\_bid\_tick \= round(book\_ticker\[book\_ticker\_exch\_clock.rn\].bid\_px / tick\_size) exch\_best\_ask\_tick \= round(book\_ticker\[book\_ticker\_exch\_clock.rn\].ask\_px / tick\_size) \# Updates the highest and lowest best bid and best ask at the exchange. if exch\_best\_bid\_tick \> high\_best\_bid\_tick: high\_best\_bid\_tick \= exch\_best\_bid\_tick if exch\_best\_ask\_tick < low\_best\_ask\_tick: low\_best\_ask\_tick \= exch\_best\_ask\_tick book\_ticker\_exch\_clock.next() elif ev \== 1: \# Updates the current local best bid and best ask. local\_best\_bid\_tick \= round(book\_ticker\[book\_ticker\_local\_clock.rn\].bid\_px / tick\_size) local\_best\_ask\_tick \= round(book\_ticker\[book\_ticker\_local\_clock.rn\].ask\_px / tick\_size) book\_ticker\_local\_clock.next() elif ev \== 2: side \= trades\[trades\_exch\_clock.rn\].side px\_tick \= round(trades\[trades\_exch\_clock.rn\].px / tick\_size) \# Updates the highest and lowest trade at the exchange. if side \== 1 and px\_tick \> high\_buy\_tick: high\_buy\_tick \= px\_tick elif side \== \-1 and px\_tick < low\_sell\_tick: low\_sell\_tick \= px\_tick trades\_exch\_clock.next() elif ev \== 3: \# Records the fill prices in ticks at the exchange between local\_ts\[t - 1\] and local\_ts\[t\]. out\_bid\_fill\_tick\[out\_t\] \= min(low\_sell\_tick + 1, low\_best\_ask\_tick) out\_ask\_fill\_tick\[out\_t\] \= max(high\_buy\_tick \- 1, high\_best\_bid\_tick) high\_buy\_tick \= INVALID\_MIN high\_best\_bid\_tick \= exch\_best\_bid\_tick low\_sell\_tick \= INVALID\_MAX low\_best\_ask\_tick \= exch\_best\_ask\_tick \# Records the current local state at local\_ts\[t\]. out\_local\_ts\[out\_t\] \= local\_clock.ts out\_best\_bid\_tick\[out\_t\] \= local\_best\_bid\_tick out\_best\_ask\_tick\[out\_t\] \= local\_best\_ask\_tick \# Order acknowledgement timestamp when the exchange receives the order request. order\_entry\_latency \= order\_latency.entry(local\_clock.ts) order\_ack\_ts \= local\_clock.ts + order\_entry\_latency \# The next local timestamp after the exchange acknowledges the order request. next\_local\_clock \= Clock(local\_ts, local\_clock.rn) while next\_local\_clock.ts < order\_ack\_ts: next\_local\_clock.next() next\_local\_ts \= next\_local\_clock.ts \# Computes the fill prices around the order acknowledgment timestamp at the exchange \# and finds the best bid and best ask at the time of acknowledgment. ( bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick, ask\_fill\_tick ) \= ack\_order( tick\_size, order\_ack\_ts, next\_local\_ts, book\_ticker\_exch\_clock.rn + 1, book\_ticker\_exch\_ts, book\_ticker, trades\_exch\_clock.rn + 1, trades\_exch\_ts, trades ) \# Records the values related to the order acknowledgment. out\_order\_ack\_ts\[out\_t\] \= order\_ack\_ts out\_bid\_fill\_tick\_ack\[out\_t\] \= bid\_fill\_tick\_ack out\_ask\_fill\_tick\_ack\[out\_t\] \= ask\_fill\_tick\_ack out\_best\_bid\_tick\_ack\[out\_t\] \= best\_bid\_tick\_ack out\_best\_ask\_tick\_ack\[out\_t\] \= best\_ask\_tick\_ack out\_bid\_fill\_tick\_after\_ack\[out\_t\] \= bid\_fill\_tick out\_ask\_fill\_tick\_after\_ack\[out\_t\] \= ask\_fill\_tick out\_t += 1 local\_clock.next() return ( out\_local\_ts\[:out\_t\], out\_best\_bid\_tick\[:out\_t\], out\_best\_ask\_tick\[:out\_t\], out\_bid\_fill\_tick\[:out\_t\], out\_ask\_fill\_tick\[:out\_t\], out\_order\_ack\_ts\[:out\_t\], out\_bid\_fill\_tick\_ack\[:out\_t\], out\_ask\_fill\_tick\_ack\[:out\_t\], out\_best\_bid\_tick\_ack\[:out\_t\], out\_best\_ask\_tick\_ack\[:out\_t\], out\_bid\_fill\_tick\_after\_ack\[:out\_t\], out\_ask\_fill\_tick\_after\_ack\[:out\_t\] ) Preprocessing Example[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Preprocessing-Example "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------- Tardis.dev provides free sample data for the first day of each month. \[10\]: \# !curl -L -o BTCUSDT\_incremental\_book\_L2\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2025/08/01/BTCUSDT.csv.gz \# !curl -L -o BTCUSDT\_trades\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/trades/2025/08/01/BTCUSDT.csv.gz \# !curl -L -o BTCUSDT\_book\_ticker\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/book\_ticker/2025/08/01/BTCUSDT.csv.gz \[11\]: import polars as pl def load\_book\_ticker(file): df \= pl.read\_csv(file) exch\_ts \= df\['timestamp'\].to\_numpy() \* 1000 local\_ts \= df\['local\_timestamp'\].to\_numpy() \* 1000 data \= df.select( pl.col('ask\_amount').alias('ask\_qty'), pl.col('ask\_price').alias('ask\_px'), pl.col('bid\_price').alias('bid\_px'), pl.col('bid\_amount').alias('bid\_qty'), ).to\_numpy(structured\=True) return exch\_ts, local\_ts, data def load\_trades(file): df \= pl.read\_csv(file) exch\_ts \= df\['timestamp'\].to\_numpy() \* 1000 local\_ts \= df\['local\_timestamp'\].to\_numpy() \* 1000 data \= df.select( pl.when(pl.col('side') \== 'buy').then(1).otherwise(\-1).alias('side'), pl.col('price').alias('px'), pl.col('amount').alias('qty'), ).to\_numpy(structured\=True) return exch\_ts, local\_ts, data \[12\]: \# For demonstration purposes, order latency artificially derived from feed latency is used. \# For more realistic backtesting, actual order latency data should be used. \# Please see https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html from hftbacktest.data.utils.tardis import convert\_fuse from hftbacktest.data.utils import feed\_order\_latency \# First, we convert the Tardis data into the normalized format to use the utility \# that generates order latency from feed latency. \# This normalized data will also be used to compare accelerated backtesting \# (which removes certain conditions) against full backtesting (which includes all conditions). convert\_fuse( 'BTCUSDT\_trades\_20250801.csv.gz', 'BTCUSDT\_incremental\_book\_L2\_20250801.csv.gz', 'BTCUSDT\_book\_ticker\_20250801.csv.gz', tick\_size\=0.1, lot\_size\=0.001, output\_filename\='BTCUSDT\_20250801.npz' ) \# Generates order latency from feed latency. \# At any given time: \# - Order entry latency = 4 × feed latency \# - Order response latency = 3 × feed latency \# Please see the document for details about the arguments. feed\_order\_latency.generate\_order\_latency( 'BTCUSDT\_20250801.npz', output\_file\='feed\_order\_latency\_20250801.npz', mul\_entry\=4, mul\_resp\=3 ) order\_latency\_data \= np.load('feed\_order\_latency\_20250801.npz')\['data'\] order\_latency \= IntpOrderLatency(order\_latency\_data) Correcting the latency Correcting the event order Saving to BTCUSDT\_20250801.npz \[13\]: tick\_size \= 0.1 lot\_size \= 0.001 \[14\]: import datetime \# 0.1 seconds running\_interval \= 100\_000\_000 start\_ts \= int(datetime.datetime(2025, 8, 1, tzinfo\=datetime.timezone.utc).timestamp() \* 1\_000\_000\_000) + running\_interval end\_ts \= int(datetime.datetime(2025, 8, 2, tzinfo\=datetime.timezone.utc).timestamp() \* 1\_000\_000\_000) \# In the final interval, to compute fill prices after order acknowledgment, \# a small buffer beyond \`end\_ts\` is necessary. local\_ts \= np.arange(start\_ts, end\_ts + 100 \* running\_interval, running\_interval) \# Additionally, the following day’s data should be concatenated to compute accurate fill prices for the final interval. def concat(a, b): ret \= \[\] for aa, bb in zip(a, b): ret.append(np.concatenate(\[aa, bb\], axis\=0)) return ret \# book\_ticker = concat( \# load\_book\_ticker('BTCUSDT\_book\_ticker\_20250501.csv.gz'), \# load\_book\_ticker('BTCUSDT\_book\_ticker\_20250502.csv.gz') \# ) \# trades = concat( \# load\_trades('BTCUSDT\_trades\_20250501.csv.gz'), \# load\_trades('BTCUSDT\_trades\_20250502.csv.gz') \# ) \# For demonstration purposes, a single day of data is used. book\_ticker \= load\_book\_ticker('BTCUSDT\_book\_ticker\_20250801.csv.gz') trades \= load\_trades('BTCUSDT\_trades\_20250801.csv.gz') \[15\]: %%time out \= preprocess\_data( tick\_size, end\_ts, local\_ts, book\_ticker\[0\], book\_ticker\[1\], book\_ticker\[2\], trades\[0\], trades\[2\], order\_latency ) CPU times: user 9.12 s, sys: 81.2 ms, total: 9.2 s Wall time: 9.17 s \[16\]: import pyarrow as pa import pyarrow.parquet as pq \# Saves the preprocessed data to a Parquet file. table \= pa.table({ 'local\_ts': out\[0\], 'best\_bid\_tick': out\[1\], 'best\_ask\_tick': out\[2\], 'bid\_fill\_tick': out\[3\], 'ask\_fill\_tick': out\[4\], 'order\_ack\_ts': out\[5\], 'bid\_fill\_tick\_ack': out\[6\], 'ask\_fill\_tick\_ack': out\[7\], 'best\_bid\_tick\_ack': out\[8\], 'best\_ask\_tick\_ack': out\[9\], 'bid\_fill\_tick\_after\_ack': out\[10\], 'ask\_fill\_tick\_after\_ack': out\[11\] }) pq.write\_table(table, 'BTCUSDT\_20250801.parquet', compression\='zstd') Accelerated Backtesting Using Preprocessed Market Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Accelerated-Backtesting-Using-Preprocessed-Market-Data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[18\]: from hftbacktest.types import record\_dtype @njit def accelerated\_backtest( relative\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ): \# req\_bid\_tick: bid order price in ticks (limit buy order with GTX) sent to the exchange, before the exchange acknowledges it. \# req\_ask\_tick: ask order price in ticks (limit sell order with GTX) sent to the exchange, before the exchange acknowledges it. \# open\_bid\_tick: bid order price in ticks acknowledged by the exchange, currently an open order in the market. \# open\_ask\_tick: ask order price in ticks acknowledged by the exchange, currently an open order in the market. # \# INVALID\_MIN and INVALID\_MAX indicate that there are no orders. # \# Example: \# If req\_bid\_tick is INVALID\_MIN and there is an open bid order, \# the open bid order will be canceled (if the cancel request reaches the exchange before the order is filled). \# When an order is filled, its price is set to INVALID\_MIN or INVALID\_MAX accordingly. req\_bid\_tick \= open\_bid\_tick \= INVALID\_MIN req\_ask\_tick \= open\_ask\_tick \= INVALID\_MAX \# corresponding order quantities. open\_bid\_qty \= req\_bid\_qty \= 0.0 open\_ask\_qty \= req\_ask\_qty \= 0.0 \# Initial state. balance \= 0.0 position \= 0.0 num\_trades \= 0 trading\_value \= 0.0 trading\_volume \= 0.0 \# Row index iterator t \= 0 \# State record for stats rec\_i \= 0 record \= np.empty(len(local\_ts), record\_dtype) while True: #-------------------------------------------------------- \# Local bot logic at \`local\_ts\[t\]\`. mid\_tick \= (best\_bid\_tick\[t\] + best\_ask\_tick\[t\]) / 2.0 mid\_px \= mid\_tick \* tick\_size notional\_position\_value \= position \* mid\_px normalized\_position \= notional\_position\_value / max\_notional\_position relative\_bid\_depth \= relative\_half\_spread + skew \* normalized\_position relative\_ask\_depth \= relative\_half\_spread \- skew \* normalized\_position req\_bid\_tick \= min(np.floor(mid\_tick \* (1.0 \- relative\_bid\_depth)), best\_bid\_tick\[t\]) req\_ask\_tick \= max(np.ceil(mid\_tick \* (1.0 + relative\_ask\_depth)), best\_ask\_tick\[t\]) req\_bid\_qty \= req\_ask\_qty \= max(round(order\_notional\_value / mid\_px / lot\_size) \* lot\_size, lot\_size) \# If the position exceeds the risk limit (max notional position), \# no orders shall be open in that direction. if normalized\_position \> 1: req\_bid\_tick \= INVALID\_MIN if normalized\_position < \-1: req\_ask\_tick \= INVALID\_MAX #-------------------------------------------------------- \# Records the current state. record\[rec\_i\].timestamp \= local\_ts\[t\] record\[rec\_i\].price \= mid\_tick \* tick\_size record\[rec\_i\].position \= position record\[rec\_i\].balance \= balance \* tick\_size record\[rec\_i\].fee \= trading\_value \* tick\_size \* fee record\[rec\_i\].num\_trades \= num\_trades record\[rec\_i\].trading\_volume \= trading\_volume record\[rec\_i\].trading\_value \= trading\_value \* tick\_size rec\_i += 1 #-------------------------------------------------------- \# Processes the exchange-side logic (order fill logic). \# If any of the requested order prices differ from the open order's price, \# it is assumed that the bot sent the order request. \# The request will be acknowledged and processed at \`order\_ack\_ts\[t\]\`. \# Otherwise, check if the open order is filled. if req\_bid\_tick != open\_bid\_tick or req\_ask\_tick != open\_ask\_tick: \# The current time is \`order\_ack\_ts\[t\]\`. order\_ack\_ts\_ \= order\_ack\_ts\[t\] \# If there are open orders with valid prices, \# checks whether they are filled before accepting the newly requested orders. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# New orders are treated as GTX. \# If the requested buy order price is greater than or equal to the best ask, \# or the requested sell order price is less than or equal to the best bid, \# the orders are rejected. \# Invalidates the price if the order is rejected. if req\_bid\_tick \>= best\_ask\_tick\_ack\[t\]: req\_bid\_tick \= INVALID\_MIN if req\_ask\_tick <= best\_bid\_tick\_ack\[t\]: req\_ask\_tick \= INVALID\_MAX \# Updates the open orders to reflect accepted orders. open\_bid\_tick \= req\_bid\_tick open\_ask\_tick \= req\_ask\_tick open\_bid\_qty \= req\_bid\_qty open\_ask\_qty \= req\_ask\_qty \# If there are open orders with valid prices, \# checks whether they are filled before the next local timestamp (\`local\_ts\[t+n\]\`) \# that is greater than the current timestamp (\`order\_ack\_ts\[t\]\`). if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# The next local timestamp must be greater than the current timestamp (\`order\_ack\_ts\[t\]\`). while t < len(local\_ts) and local\_ts\[t\] < order\_ack\_ts\_: t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break else: \# Checks if the open orders are filled between two local timestamps. \# The next row of data contains the bid fill price (in ticks) and ask fill price (in ticks) \# for that interval (step). t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break \# # If there are open orders with valid prices, checks if they are filled. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX return record\[:rec\_i\] \[19\]: %%time table \= pq.read\_table('BTCUSDT\_20250801.parquet') local\_ts \= table\['local\_ts'\].to\_numpy() best\_bid\_tick \= table\['best\_bid\_tick'\].to\_numpy() best\_ask\_tick \= table\['best\_ask\_tick'\].to\_numpy() bid\_fill\_tick \= table\['bid\_fill\_tick'\].to\_numpy() ask\_fill\_tick \= table\['ask\_fill\_tick'\].to\_numpy() order\_ack\_ts \= table\['order\_ack\_ts'\].to\_numpy() bid\_fill\_tick\_ack \= table\['bid\_fill\_tick\_ack'\].to\_numpy() ask\_fill\_tick\_ack \= table\['ask\_fill\_tick\_ack'\].to\_numpy() best\_bid\_tick\_ack \= table\['best\_bid\_tick\_ack'\].to\_numpy() best\_ask\_tick\_ack \= table\['best\_ask\_tick\_ack'\].to\_numpy() bid\_fill\_tick\_after\_ack \= table\['bid\_fill\_tick\_after\_ack'\].to\_numpy() ask\_fill\_tick\_after\_ack \= table\['ask\_fill\_tick\_after\_ack'\].to\_numpy() relative\_half\_spread \= 0.00025 skew \= relative\_half\_spread order\_notional\_value \= 50000 max\_notional\_position \= order\_notional\_value \* 20 fee\_per\_value \= \-0.00005 \# 0.005% rebates record \= accelerated\_backtest( relative\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee\_per\_value, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ) CPU times: user 541 ms, sys: 95.8 ms, total: 637 ms Wall time: 560 ms \[20\]: from hftbacktest.stats import LinearAssetRecord stats \= ( LinearAssetRecord(record) .resample('1s') .stats(book\_size\=max\_notional\_position) ) stats.summary() \[20\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 7.60934 | 10.894142 | 0.001196 | 0.003273 | 350.0 | 17.49908 | 0.365565 | 0.000068 | 347732.5629 | \[21\]: stats.plot() \[21\]: ![../_images/tutorials_Accelerated_Backtesting_21_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Accelerated_Backtesting_21_0.png) Comparison of Backtesting Results with Full Backtesting[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Comparison-of-Backtesting-Results-with-Full-Backtesting "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[23\]: from numba import uint64 from numba.typed import Dict from hftbacktest import ( GTX, LIMIT, BUY, SELL, Recorder, BacktestAsset, ROIVectorMarketDepthBacktest ) from hftbacktest.stats import LinearAssetRecord @njit def basic\_mm( hbt, stat, relative\_half\_spread, skew, interval, order\_notional\_value, max\_notional\_position, grid\_num, grid\_interval\_tick ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) skip \= False orders \= hbt.orders(asset\_no) order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() if not order.cancellable: skip \= True break if skip: continue depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) mid\_tick \= (depth.best\_bid\_tick + depth.best\_ask\_tick) / 2.0 mid\_px \= mid\_tick \* tick\_size #-------------------------------------------------------- \# Computes bid price and ask price. notional\_position\_value \= position \* mid\_px normalized\_position \= notional\_position\_value / max\_notional\_position relative\_bid\_depth \= relative\_half\_spread + skew \* normalized\_position relative\_ask\_depth \= relative\_half\_spread \- skew \* normalized\_position bid\_price\_tick \= min(np.floor(mid\_tick \* (1.0 \- relative\_bid\_depth)), depth.best\_bid\_tick) ask\_price\_tick \= max(np.ceil(mid\_tick \* (1.0 + relative\_ask\_depth)), depth.best\_ask\_tick) order\_qty \= max(round(order\_notional\_value / mid\_px / lot\_size) \* lot\_size, lot\_size) #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if normalized\_position < 1: for i in range(grid\_num): \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price\_tick \* tick\_size bid\_price\_tick \-= grid\_interval\_tick \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if normalized\_position \> \-1: for i in range(grid\_num): \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price\_tick \* tick\_size ask\_price\_tick += grid\_interval\_tick order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[24\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 grid\_num \= 1 grid\_interval\_tick \= 1 asset \= ( BacktestAsset() .data(\['BTCUSDT\_20250801.npz'\]) .linear\_asset(1.0) .intp\_order\_latency(\['feed\_order\_latency\_20250801.npz'\]) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) basic\_mm( hbt, recorder.recorder, relative\_half\_spread, skew, running\_interval, order\_notional\_value, max\_notional\_position, grid\_num, grid\_interval\_tick ) \_ \= hbt.close() CPU times: user 1min 45s, sys: 3.73 s, total: 1min 49s Wall time: 1min 49s \[25\]: data \= recorder.get(0) stats \= ( LinearAssetRecord(data) .resample('1s') .stats(book\_size\=max\_notional\_position) ) stats.summary() \[25\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-01 23:59:59 | 7.033126 | 10.104532 | 0.00119 | 0.00299 | 348.004028 | 17.399667 | 0.397857 | 0.000068 | 349253.6362 | \[26\]: stats.plot() \[26\]: ![../_images/tutorials_Accelerated_Backtesting_26_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Accelerated_Backtesting_26_0.png) Firstly, accelerated backtest: 416 ms; full backtest: 1 min 49 s — roughly 260× faster. You can see that there are differences in the detailed numbers, but overall, the results show similar characteristics in terms of position and equity. The differences can become larger depending on the strategy’s characteristics—especially, as mentioned earlier, for assets where fills in the queue is crucial, such as those with a large tick size. Therefore, it is still important to verify the results from accelerated backtesting against those from full backtesting. Precompute Signal - Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Precompute-Signal---Order-Book-Imbalance "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Similarly, precomputing the signal speeds up backtesting and allows rapid iteration to test ideas and tune parameters. \[29\]: @njit def depth\_below(depth, start, end, roi\_lb\_tick): for i in range(start, end \- 1, \-1): if depth\[i \- roi\_lb\_tick\] \> 0: return i return INVALID\_MIN @njit def depth\_above(depth, start, end, roi\_lb\_tick): for i in range(start, end + 1): if depth\[i \- roi\_lb\_tick\] \> 0: return i return INVALID\_MAX @njit def precompute\_obi( tick\_size, lot\_size, roi\_lb, roi\_ub, end\_ts, local\_ts, depth\_local\_ts, depth, depth\_range ): roi\_lb\_tick \= round(roi\_lb / tick\_size) roi\_ub\_tick \= round(roi\_ub / tick\_size) bid\_depth \= np.zeros(roi\_ub\_tick \- roi\_lb\_tick, np.float64) ask\_depth \= np.zeros(roi\_ub\_tick \- roi\_lb\_tick, np.float64) best\_bid\_tick \= INVALID\_MIN best\_ask\_tick \= INVALID\_MAX low\_bid\_tick \= INVALID\_MAX high\_ask\_tick \= INVALID\_MIN depth\_local\_clock \= Clock(depth\_local\_ts, 0) local\_clock \= Clock(local\_ts, 0) out\_t \= 0 out\_mid\_tick \= np.empty(len(local\_ts), np.float64) out\_bid\_qty \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_bid\_weighted \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_ask\_qty \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_ask\_weighted \= np.empty((len(local\_ts), len(depth\_range)), np.float64) while local\_clock.ts <= end\_ts: ev \= select\_event(np.asarray(\[\ depth\_local\_clock.ts,\ local\_clock.ts\ \])) if ev \== \-1: raise ValueError elif ev \== 0: \# Builds the market depth. side \= depth\[depth\_local\_clock.rn\].side px\_tick \= round(depth\[depth\_local\_clock.rn\].px / tick\_size) \# Skips processing if the depth update price falls outside the defined range of interest. if px\_tick \> roi\_ub\_tick or px\_tick < roi\_lb\_tick: depth\_local\_clock.next() continue qty \= depth\[depth\_local\_clock.rn\].qty qty\_lot \= round(qty / lot\_size) if side \== 1: bid\_depth\[px\_tick \- roi\_lb\_tick\] \= qty if px\_tick < low\_bid\_tick: low\_bid\_tick \= px\_tick if px\_tick \> best\_bid\_tick and qty\_lot \> 0: \# Updates the best bid if the bid price is higher than the current best bid. best\_bid\_tick \= px\_tick if best\_bid\_tick \>= best\_ask\_tick: \# When the best bid is greater than or equal to the best ask, \# updates the best ask to the lowest ask above the new best bid. best\_ask\_tick \= depth\_above(ask\_depth, best\_bid\_tick + 1, high\_ask\_tick, roi\_lb\_tick) elif px\_tick \== best\_bid\_tick and qty\_lot \== 0: \# Finds the new best bid if the current best bid is deleted. best\_bid\_tick \= depth\_below(bid\_depth, px\_tick, low\_bid\_tick, roi\_lb\_tick) else: ask\_depth\[px\_tick \- roi\_lb\_tick\] \= qty if px\_tick \> high\_ask\_tick: high\_ask\_tick \= px\_tick if px\_tick < best\_ask\_tick and qty\_lot \> 0: \# Updates the best ask if the ask price is lower than the current best ask. best\_ask\_tick \= px\_tick if best\_ask\_tick <= best\_bid\_tick: \# When the best ask is less than or equal to the best bid, \# updates the best bid to the highest bid below the new best ask. best\_bid\_tick \= depth\_below(bid\_depth, best\_ask\_tick \- 1, low\_bid\_tick, roi\_lb\_tick) elif px\_tick \== best\_ask\_tick and qty\_lot \== 0: \# Finds the best ask if the current best ask is deleted. best\_ask\_tick \= depth\_above(ask\_depth, px\_tick, high\_ask\_tick, roi\_lb\_tick) depth\_local\_clock.next() elif ev \== 1: if best\_bid\_tick \== INVALID\_MIN or best\_ask\_tick \== INVALID\_MAX: mid\_tick \= np.nan out\_bid\_qty\[out\_t, :\] \= np.nan out\_bid\_weighted\[out\_t, :\] \= np.nan out\_ask\_qty\[out\_t, :\] \= np.nan out\_ask\_weighted\[out\_t, :\] \= np.nan else: mid\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2 \# Computes the order book imbalance for the depth range from the mid-price. \# Please see https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html # \# To compute order-book imbalance values, aggregate over depth levels d, where d \# denotes the tick distance from the mid price; d is symmetric from the mid tick. \# mid\_tick, Sum{Q\_bid\[d\]}, Sum{Q\_ask\[d\]}, Sum{d \* Q\_bid\[d\]}, Sum{d \* Q\_ask\[d\]} # \# VAMP = (Sum{P\_bid\[d\] \* Q\_ask\[d\]} + Sum{P\_ask\[d\] \* Q\_bid\[d\]}) / (Sum{Q\_bid\[d\]} + Sum{Q\_ask\[d\]}) \# = tick\_size \* (mid\_tick \* Sum{Q\_ask\[d\]} - Sum{d \* Q\_ask\[d\]} + mid\_tick \* Sum{Q\_bid\[d\]} + Sum{d \* Q\_bid\[d\]}) \# / (Sum{Q\_bid\[d\]} + Sum{Q\_ask\[d\]}) # \# Bid\_effective = Sum{P\_bid\[d\] \* Q\_bid\[d\]} / Sum{Q\_bid\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_bid\[d\]} - Sum{d \* Q\_bid\[d\]}) / Sum{Q\_bid\[d\]} \# Ask\_effective = Sum{P\_ask\[d\] \* Q\_ask\[d\]} / Sum{Q\_ask\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_ask\[d\]} + Sum{d \* Q\_ask\[d\]}) / Sum{Q\_ask\[d\]} i \= 0 bid\_qty \= 0.0 bid\_weighted \= 0.0 for d in range(best\_bid\_tick, low\_bid\_tick \- 1, \-1): bid\_qty += bid\_depth\[d \- roi\_lb\_tick\] bid\_weighted += bid\_depth\[d \- roi\_lb\_tick\] \* (mid\_tick \- d) if d < mid\_tick \* (1 \- depth\_range\[i\]): out\_bid\_qty\[out\_t, i\] \= bid\_qty out\_bid\_weighted\[out\_t, i\] \= bid\_weighted i += 1 if i \== len(depth\_range): break i \= 0 ask\_qty \= 0.0 ask\_weighted \= 0.0 for d in range(best\_ask\_tick, high\_ask\_tick + 1): ask\_qty += ask\_depth\[d \- roi\_lb\_tick\] ask\_weighted += ask\_depth\[d \- roi\_lb\_tick\] \* (d \- mid\_tick) if d \> mid\_tick \* (1 + depth\_range\[i\]): out\_ask\_qty\[out\_t, i\] \= ask\_qty out\_ask\_weighted\[out\_t, i\] \= ask\_weighted i += 1 if i \== len(depth\_range): break out\_mid\_tick\[out\_t\] \= mid\_tick out\_t += 1 local\_clock.next() return out\_mid\_tick\[:out\_t\], out\_bid\_qty\[:out\_t\], out\_bid\_weighted\[:out\_t\], out\_ask\_qty\[:out\_t\], out\_ask\_weighted\[:out\_t\] \[30\]: def load\_incremental\_book(file): df \= pl.read\_csv(file) exch\_ts \= df\['timestamp'\].to\_numpy() \* 1000 local\_ts \= df\['local\_timestamp'\].to\_numpy() \* 1000 data \= df.select( pl.col('is\_snapshot').cast(pl.Int32), pl.when(pl.col('side') \== 'bid').then(1).otherwise(\-1).alias('side'), pl.col('price').alias('px'), pl.col('amount').alias('qty'), ).to\_numpy(structured\=True) return exch\_ts, local\_ts, data \# Because the BTCUSDT market depth data is very large, converting it using Polars often crashes due to memory limits. \# Parsing directly in Python is slower but uses less memory. import gzip def load\_incremental\_book(file, buffer\_size\=200\_000\_000): depth\_dtype \= np.dtype(\[\ ('is\_snapshot', np.int32),\ ('side', np.int32),\ ('px', np.float64),\ ('qty', np.float64)\ \]) exch\_ts \= np.empty(buffer\_size, np.int64) local\_ts \= np.empty(buffer\_size, np.int64) data \= np.empty(buffer\_size, depth\_dtype) with gzip.open(file) as f: header \= True i \= 0 while True: line \= f.readline() if not line: break if header: header \= False continue columns \= line.decode().split(',') if i \== buffer\_size: raise MemoryError('Not enough buffer size to load data') exch\_ts\[i\] \= int(columns\[2\]) \* 1000 local\_ts\[i\] \= int(columns\[3\]) \* 1000 data\[i\]\[0\] \= 1 if columns\[4\] \== 'true' else 0 data\[i\]\[1\] \= 1 if columns\[5\] \== 'bid' else \-1 data\[i\]\[2\] \= float(columns\[6\]) data\[i\]\[3\] \= float(columns\[7\]) i += 1 return exch\_ts\[:i\], local\_ts\[:i\], data\[:i\] \[31\]: depth \= load\_incremental\_book('BTCUSDT\_incremental\_book\_L2\_20250801.csv.gz') local\_ts \= np.arange(start\_ts, end\_ts + running\_interval, running\_interval) \[32\]: roi\_lb \= 50000 roi\_ub \= 150000 depth\_range \= np.asarray(\[0.0025, 0.005, 0.0075, 0.01, 0.015, 0.02, 0.025, 0.03\]) ( out\_mid\_tick, out\_bid\_qty, out\_bid\_weighted, out\_ask\_qty, out\_ask\_weighted ) \= precompute\_obi( tick\_size, lot\_size, roi\_lb, roi\_ub, end\_ts, local\_ts, depth\[1\], depth\[2\], depth\_range ) table\_def \= { 'local\_ts': local\_ts, 'mid\_tick': out\_mid\_tick } for i, d in enumerate(depth\_range): table\_def\[f'bid\_qty\_{d}'\] \= out\_bid\_qty\[:, i\] table\_def\[f'bid\_weighted\_{d}'\] \= out\_bid\_weighted\[:, i\] table\_def\[f'ask\_qty\_{d}'\] \= out\_ask\_qty\[:, i\] table\_def\[f'ask\_weighted\_{d}'\] \= out\_ask\_weighted\[:, i\] pq.write\_table(pa.table(table\_def), f'BTCUSDT\_obi\_20250801.parquet', compression\='zstd') \[33\]: table \= pq.read\_table('BTCUSDT\_obi\_20250801.parquet') local\_ts \= table\['local\_ts'\].to\_numpy() mid\_tick \= table\['mid\_tick'\].to\_numpy() \# VAMP = (Sum{P\_bid\[d\] \* Q\_ask\[d\]} + Sum{P\_ask\[d\] \* Q\_bid\[d\]}) / (Sum{Q\_bid\[d\]} + Sum(Q\_ask\[d\])} \# Sum{P\_bid\[d\] \* Q\_ask\[d\]} + Sum{P\_ask\[d\] \* Q\_bid\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_ask\[d\]} - Sum{d \* Q\_ask\[d\]} + mid\_tick \* Sum{Q\_bid\[d\]} + Sum{d \* Q\_bid\[d\]}) # \# P\_effective\_bid = Sum{P\_bid\[d\] \* Q\_bid\[d\]} / Sum{Q\_bid\[d\]} \# Sum{P\_bid\[d\] \* Q\_bid\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_bid\[d\]} - Sum{d \* Q\_bid\[d\]}) depth\_range \= 0.005 bid\_qty \= table\[f'bid\_qty\_{depth\_range}'\].to\_numpy() ask\_qty \= table\[f'ask\_qty\_{depth\_range}'\].to\_numpy() bid\_weighted \= table\[f'bid\_weighted\_{depth\_range}'\].to\_numpy() ask\_weighted \= table\[f'ask\_weighted\_{depth\_range}'\].to\_numpy() vamp \= np.divide( tick\_size \* ( mid\_tick \* ask\_qty \- ask\_weighted + mid\_tick \* bid\_qty + bid\_weighted ), bid\_qty + ask\_qty ) bid\_eff \= tick\_size \* (mid\_tick \* bid\_qty \- bid\_weighted) / bid\_qty ask\_eff \= tick\_size \* (mid\_tick \* ask\_qty + ask\_weighted) / ask\_qty vamp\_eff \= np.divide( bid\_eff \* ask\_qty + ask\_eff \* bid\_qty, bid\_qty + ask\_qty ) /tmp/ipykernel\_17848/2337632004.py:20: RuntimeWarning: invalid value encountered in divide vamp = np.divide( /tmp/ipykernel\_17848/2337632004.py:28: RuntimeWarning: invalid value encountered in divide bid\_eff = tick\_size \* (mid\_tick \* bid\_qty - bid\_weighted) / bid\_qty /tmp/ipykernel\_17848/2337632004.py:29: RuntimeWarning: invalid value encountered in divide ask\_eff = tick\_size \* (mid\_tick \* ask\_qty + ask\_weighted) / ask\_qty \[34\]: from matplotlib import pyplot as plt start \= 35000 end \= 40000 ts \= pl.Series("timestamp", local\_ts) ts\_dt \= ts.cast(pl.Datetime('ns')) plt.plot(ts\_dt\[start:end\], vamp\[start:end\]) plt.plot(ts\_dt\[start:end\], vamp\_eff\[start:end\]) plt.plot(ts\_dt\[start:end\], mid\_tick\[start:end\] \* tick\_size) plt.legend(\['VAMP', 'VAMP$\_{effective}$', 'Mid Price'\]) \[34\]: ![../_images/tutorials_Accelerated_Backtesting_34_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Accelerated_Backtesting_34_1.png) Accelerated Backtesting - Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html#Accelerated-Backtesting---Order-Book-Imbalance "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Using the precomputed order book imbalance, we perform accelerated backtesting. The only modification is that quoting based on `mid_tick` is replaced with `fair_px_tick`. VAMPeffective is selected for demonstration purposes. As illustrated in [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) , there are many different ways to compute order book imbalance. The key is to identify which formulation provides the stronger signal and which parameters lead to better performance, as these may vary depending on the strategy and how the signal is monetized. At the end of this section, you can observe that performance improves compared to `mid_tick`; however, it is ultimately necessary to evaluate results over longer periods. Such comparisons and analyses require repeated, iterative backtesting — made feasible through this accelerated backtesting framework. \[36\]: @njit def accelerated\_backtest( fair\_px\_tick, relative\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ): \# req\_bid\_tick: bid order price in ticks (limit buy order with GTX) sent to the exchange, before the exchange acknowledges it. \# req\_ask\_tick: ask order price in ticks (limit sell order with GTX) sent to the exchange, before the exchange acknowledges it. \# open\_bid\_tick: bid order price in ticks acknowledged by the exchange, currently an open order in the market. \# open\_ask\_tick: ask order price in ticks acknowledged by the exchange, currently an open order in the market. # \# INVALID\_MIN and INVALID\_MAX indicate that there are no orders. # \# Example: \# If req\_bid\_tick is INVALID\_MIN and there is an open bid order, \# the open bid order will be canceled (if the cancel request reaches the exchange before the order is filled). \# When an order is filled, its price is set to INVALID\_MIN or INVALID\_MAX accordingly. req\_bid\_tick \= open\_bid\_tick \= INVALID\_MIN req\_ask\_tick \= open\_ask\_tick \= INVALID\_MAX \# corresponding order quantities. open\_bid\_qty \= req\_bid\_qty \= 0.0 open\_ask\_qty \= req\_ask\_qty \= 0.0 \# Initial state. balance \= 0.0 position \= 0.0 num\_trades \= 0 trading\_value \= 0.0 trading\_volume \= 0.0 \# Row index iterator t \= 0 \# State record for stats rec\_i \= 0 record \= np.empty(len(local\_ts), record\_dtype) while True: #-------------------------------------------------------- \# Local bot logic at \`local\_ts\[t\]\`. mid\_tick \= (best\_bid\_tick\[t\] + best\_ask\_tick\[t\]) / 2.0 mid\_px \= mid\_tick \* tick\_size notional\_position\_value \= position \* mid\_px normalized\_position \= notional\_position\_value / max\_notional\_position relative\_bid\_depth \= relative\_half\_spread + skew \* normalized\_position relative\_ask\_depth \= relative\_half\_spread \- skew \* normalized\_position req\_bid\_tick \= min(np.floor(fair\_px\_tick\[t\] \* (1.0 \- relative\_bid\_depth)), best\_bid\_tick\[t\]) req\_ask\_tick \= max(np.ceil(fair\_px\_tick\[t\] \* (1.0 + relative\_ask\_depth)), best\_ask\_tick\[t\]) req\_bid\_qty \= req\_ask\_qty \= max(round(order\_notional\_value / mid\_px / lot\_size) \* lot\_size, lot\_size) \# If the position exceeds the risk limit (max notional position), \# no orders shall be open in that direction. if normalized\_position \> 1: req\_bid\_tick \= INVALID\_MIN if normalized\_position < \-1: req\_ask\_tick \= INVALID\_MAX #-------------------------------------------------------- \# Records the current state. record\[rec\_i\].timestamp \= local\_ts\[t\] record\[rec\_i\].price \= mid\_tick \* tick\_size record\[rec\_i\].position \= position record\[rec\_i\].balance \= balance \* tick\_size record\[rec\_i\].fee \= trading\_value \* tick\_size \* fee record\[rec\_i\].num\_trades \= num\_trades record\[rec\_i\].trading\_volume \= trading\_volume record\[rec\_i\].trading\_value \= trading\_value \* tick\_size rec\_i += 1 #-------------------------------------------------------- \# Processes the exchange-side logic (order fill logic). \# If any of the requested order prices differ from the open order's price, \# it is assumed that the bot sent the order request. \# The request will be acknowledged and processed at \`order\_ack\_ts\[t\]\`. \# Otherwise, check if the open order is filled. if req\_bid\_tick != open\_bid\_tick or req\_ask\_tick != open\_ask\_tick: \# The current time is \`order\_ack\_ts\[t\]\`. order\_ack\_ts\_ \= order\_ack\_ts\[t\] \# If there are open orders with valid prices, \# checks whether they are filled before accepting the newly requested orders. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# New orders are treated as GTX. \# If the requested buy order price is greater than or equal to the best ask, \# or the requested sell order price is less than or equal to the best bid, \# the orders are rejected. \# Invalidates the price if the order is rejected. if req\_bid\_tick \>= best\_ask\_tick\_ack\[t\]: req\_bid\_tick \= INVALID\_MIN if req\_ask\_tick <= best\_bid\_tick\_ack\[t\]: req\_ask\_tick \= INVALID\_MAX \# Updates the open orders to reflect accepted orders. open\_bid\_tick \= req\_bid\_tick open\_ask\_tick \= req\_ask\_tick open\_bid\_qty \= req\_bid\_qty open\_ask\_qty \= req\_ask\_qty \# If there are open orders with valid prices, \# checks whether they are filled before the next local timestamp (\`local\_ts\[t+n\]\`) \# that is greater than the current timestamp (\`order\_ack\_ts\[t\]\`). if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# The next local timestamp must be greater than the current timestamp (\`order\_ack\_ts\[t\]\`). while t < len(local\_ts) and local\_ts\[t\] < order\_ack\_ts\_: t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break else: \# Checks if the open orders are filled between two local timestamps. \# The next row of data contains the bid fill price (in ticks) and ask fill price (in ticks) \# for that interval (step). t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break \# # If there are open orders with valid prices, checks if they are filled. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX return record\[:rec\_i\] \[37\]: %%time record \= accelerated\_backtest( vamp\_eff / tick\_size, relative\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee\_per\_value, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ) CPU times: user 241 ms, sys: 10.9 ms, total: 252 ms Wall time: 250 ms \[38\]: from hftbacktest.stats import LinearAssetRecord stats \= ( LinearAssetRecord(record) .resample('1s') .stats(book\_size\=max\_notional\_position) ) stats.summary() \[38\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 18.879569 | 26.861534 | 0.018396 | 0.012351 | 1492.0 | 74.602088 | 1.489513 | 0.000247 | 1.0547e6 | \[39\]: stats.plot() \[39\]: ![../_images/tutorials_Accelerated_Backtesting_39_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Accelerated_Backtesting_39_0.png) In the next tutorial, we will explore a more generalized framework to forecasting and fair value pricing in research based on [A Comprehensive Framework for Pricing Models](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html#A-Comprehensive-Framework-for-Pricing-Models) . --- # Research Pricing Framework — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Research Pricing Framework * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Pricing%20Framework.ipynb.txt) * * * Research Pricing Framework[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Research-Pricing-Framework "Link to this heading") ================================================================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------- DRAFT VERSION Pricing involves evaluating the fair value price or forecasting the future price whenever an action is required. The examples shown so far have been based on fixed time intervals (i.e., a physical clock), meaning that the price is evaluated at regular time frames. This framework, however, can be extended beyond time-based sampling. For example, one may use a volume clock, an event clock, or even react to every tick. For now, we will keep the focus on fixed intervals, which allow accelerated backtesting through preprocessing of market data and can be applied across multiple assets, while leaving further extensions for future exploration. From a research perspective, the fair value can be quickly computed using pre-resampled data. For instance, if prices are resampled at 100ms intervals (our running interval in the tutorials), the fair price can be readily computed within that framework. We will now demonstrate how this framework can be generalized for research purposes. An interesting finding is that pricing models on the returns of a primary exchange can often be transferred to other venues, outperforming models calibrated individually for each exchange. For example, a model on Binance Futures can be effectively applied to Bybit, OKX, Hyperliquid, and other venues. This observation also points to the existence of lead-lag relationships between exchanges, reinforcing the idea of cross-exchange pricing dependencies. While some variation may arise due to differences in product structures, fee schedules, or exchange-specific microstructure, the overall transferability can remain strong. The extent of divergence depends heavily on the time horizon of the model. In these contexts, latency becomes a decisive factor—faster transmission of information across exchanges can materially improve performance, a result that can be confirmed through low-latency backtesting. This transferability extends beyond exchanges to individual trading pairs. A pricing model on BTCUSDT can also be applied to other pairs, such as ETHUSDT. This effect is primarily driven by the strong correlation between BTCUSDT and these pairs. The degree of dependence—how strongly one pair tracks another—differs by pair and must be quantified. Publicly available peer group analyses can serve as a useful starting point for understanding these dynamics. Finally, it is always essential to validate whether the strategy is robust over longer periods. Sustained performance over time is the ultimate test of a pricing. Prepare Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Prepare-Data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------- For demonstration purposes, We will use futures together with their underlying spot data, price returns, and order book imbalance—which, along with funding, represent the most fundamental price drivers—for pricing. [Tardis.dev](https://www.tardis.dev/) provides free access to data from the first day of each month. Download the Binance Futures dataset. \[1\]: \# symbol\_list = \['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT'\] \# !mkdir futures \# for symbol in symbol\_list: \# !curl -L -o futures/{symbol}\_incremental\_book\_L2\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2025/08/01/{symbol}.csv.gz \# !curl -L -o futures/{symbol}\_trades\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/trades/2025/08/01/{symbol}.csv.gz \# !curl -L -o futures/{symbol}\_book\_ticker\_20250801.csv.gz https://datasets.tardis.dev/v1/binance-futures/book\_ticker/2025/08/01/{symbol}.csv.gz Download the corresponding USDT spot data. \[2\]: \# symbol\_list = \['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT'\] \# !mkdir spot \# for symbol in symbol\_list: \# !curl -L -o spot/{symbol}\_book\_ticker\_20250801.csv.gz https://datasets.tardis.dev/v1/binance/book\_ticker/2025/08/01/{symbol}.csv.gz BTCFDUSD spot is traded more actively than BTCUSDT spot on Binance, likely due to its zero-fee structure. The higher trading volume suggests that it could be a stronger driver than BTCUSDT spot. Download the corresponding FDUSD spot data. \[3\]: \# symbol\_list = \['BTCFDUSD', 'ETHFDUSD', 'SOLFDUSD', 'DOGEFDUSD', 'XRPFDUSD'\] \# for symbol in symbol\_list: \# !curl -L -o spot/{symbol}\_book\_ticker\_20250801.csv.gz https://datasets.tardis.dev/v1/binance/book\_ticker/2025/08/01/{symbol}.csv.gz Price Return Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Price-Return-Data "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------- In our pricing framework, the reaction interval is set to 0.1 seconds, and the mid-price is resampled at this frequency. The resulting data has the shape \[time (0.1s intervals), pairs\]. \[4\]: import numpy as np import polars as pl import datetime def load\_price(date, market, symbol\_list): start \= date \= datetime.datetime.strptime(str(date), '%Y-%m-%d').replace(tzinfo\=datetime.timezone.utc) end \= start + datetime.timedelta(days\=1) running\_interval \= 100\_000\_000 start\_ts \= int(start.timestamp() \* 1\_000\_000\_000) + running\_interval end\_ts \= int(end.timestamp() \* 1\_000\_000\_000) resample\_ts \= ( pl.Series( 'local\_timestamp', np.arange(start\_ts, end\_ts + running\_interval, running\_interval) ) .cast(pl.Datetime('ns')) .cast(pl.Datetime('us')) ) px \= \[\] for symbol in symbol\_list: df \= ( pl.read\_csv(f'{market}/{symbol}\_book\_ticker\_{date.strftime("%Y%m%d")}.csv.gz') .with\_columns( pl.col('local\_timestamp').cast(pl.Datetime) ) .group\_by\_dynamic( index\_column\='local\_timestamp', every\='100ms', period\='100ms', offset\='0s', closed\='right', label\='right' ) .agg( ((pl.col('bid\_price') + pl.col('ask\_price')) / 2.0).last().alias('mid\_px'), ) ) df\_resample \= ( resample\_ts.to\_frame() .join(df, on\='local\_timestamp', how\='left') .fill\_null(strategy\='forward') ) ts \= ( df\_resample\['local\_timestamp'\] .dt .replace\_time\_zone('UTC') .cast(pl.Int64) \* 1\_000\_000 ) px.append(df\_resample.select(pl.col('mid\_px').alias(symbol))) return resample\_ts, pl.concat(px, how\='horizontal') \[5\]: date \= '2025-08-01' \[6\]: ts, df\_futures \= load\_price(date, 'futures', \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\]) \[7\]: df\_futures \[7\]: shape: (864\_000, 5) | BTCUSDT | ETHUSDT | XRPUSDT | SOLUSDT | DOGEUSDT | | --- | --- | --- | --- | --- | | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | | 115697.35 | 3696.575 | 3.02035 | 172.155 | 0.209665 | | 115697.35 | 3696.575 | 3.02035 | 172.155 | 0.209665 | | 115697.35 | 3696.575 | 3.02035 | 172.155 | 0.209665 | | 115697.35 | 3696.575 | 3.02035 | 172.155 | 0.209665 | | 115697.35 | 3696.575 | 3.02035 | 172.155 | 0.209665 | | … | … | … | … | … | | 113244.75 | 3486.415 | 2.96015 | 162.605 | 0.200855 | | 113244.75 | 3486.415 | 2.96015 | 162.605 | 0.200855 | | 113244.75 | 3486.415 | 2.96015 | 162.605 | 0.200855 | | 113244.75 | 3486.415 | 2.96015 | 162.605 | 0.200855 | | 113244.75 | 3486.415 | 2.96015 | 162.605 | 0.200855 | \[8\]: \# Resampled time ts \[8\]: shape: (864\_000,) | local\_timestamp | | --- | | datetime\[μs\] | | --- | | 2025-08-01 00:00:00.100 | | 2025-08-01 00:00:00.200 | | 2025-08-01 00:00:00.300 | | 2025-08-01 00:00:00.400 | | 2025-08-01 00:00:00.500 | | … | | 2025-08-01 23:59:59.600 | | 2025-08-01 23:59:59.700 | | 2025-08-01 23:59:59.800 | | 2025-08-01 23:59:59.900 | | 2025-08-02 00:00:00 | The corresponding spot price matrices are loaded as well. As discussed in the [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) tutorial, on Binance, the FDUSD spot market has a larger trading volume than the USDT spot market, likely due to its zero-fee structure. It’s important to find the primary driver presumably the largest market has more chance play this role. This higher trading volume suggests that the FDUSD spot market may serve as the primary market. Thus, not just any spot underlying in small exchange, but the one in primary exchange works as a primary driver, or at least, it needs to be aggregated across the market to get better proxy of price discovery happening in the underlying spot. But in many cases, even in spot, the primary spot leads the other spots so simply aggregation wouldn’t be helpful since the secondary market move is just reaction to follow the primary market. Hence, it’s important to find the relation between spots, and futures and also cross-assets. \[9\]: ts, df\_spot\_usdt \= load\_price(date, 'spot', \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\]) ts, df\_spot\_fdusd \= load\_price(date, 'spot', \['BTCFDUSD', 'ETHFDUSD', 'XRPFDUSD', 'SOLFDUSD', 'DOGEFDUSD'\]) Return matrices are derived from price matrices sampled at 0.1-second intervals, resulting in returns measured at the same frequency. Longer-horizon returns can be approximated by aggregating the 0.1-second returns. For instance: `df_fut_returns_1min = rolling_sum(df_fut_returns, 600).` \[10\]: df\_fut\_returns \= df\_futures / df\_futures.shift(1) \- 1 df\_usdt\_returns \= df\_spot\_usdt / df\_spot\_usdt.shift(1) \- 1 df\_fdusd\_returns \= df\_spot\_fdusd / df\_spot\_fdusd.shift(1) \- 1 Order Book Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Order-Book-Data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------- In the same way demonstrated in [the previous tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Accelerated%20Backtesting.html) , we precompute the order book imbalance components. \[11\]: import pandas as pd from numba import njit from matplotlib import pyplot as plt import numba as nb from numba.experimental import jitclass INVALID\_MIN \= 0 INVALID\_MAX \= np.iinfo(np.int64).max \- 1 @jitclass class Clock: timestamp: nb.int64\[:\] rn: nb.int64 ts: nb.int64 def \_\_init\_\_(self, timestamp, rn): self.timestamp \= timestamp self.rn \= rn if self.rn \>= len(self.timestamp): self.ts \= INVALID\_MAX else: self.ts \= self.timestamp\[self.rn\] def next(self): if self.rn \== len(self.timestamp) \- 1: self.ts \= INVALID\_MAX else: self.rn += 1 self.ts \= self.timestamp\[self.rn\] @njit def select\_event(timestamps): \# Finds the earliest timestamped event to process first. earliest\_ts \= INVALID\_MAX ev \= \-1 for i in range(len(timestamps)): if timestamps\[i\] < earliest\_ts: earliest\_ts \= timestamps\[i\] ev \= i return ev @njit def depth\_below(depth, start, end, roi\_lb\_tick): for i in range(start, end \- 1, \-1): if depth\[i \- roi\_lb\_tick\] \> 0: return i return INVALID\_MIN @njit def depth\_above(depth, start, end, roi\_lb\_tick): for i in range(start, end + 1): if depth\[i \- roi\_lb\_tick\] \> 0: return i return INVALID\_MAX @njit def precompute\_obi( tick\_size, lot\_size, roi\_lb, roi\_ub, end\_ts, local\_ts, depth\_local\_ts, depth, depth\_range ): roi\_lb\_tick \= round(roi\_lb / tick\_size) roi\_ub\_tick \= round(roi\_ub / tick\_size) bid\_depth \= np.zeros(roi\_ub\_tick \- roi\_lb\_tick, np.float64) ask\_depth \= np.zeros(roi\_ub\_tick \- roi\_lb\_tick, np.float64) best\_bid\_tick \= INVALID\_MIN best\_ask\_tick \= INVALID\_MAX low\_bid\_tick \= INVALID\_MAX high\_ask\_tick \= INVALID\_MIN depth\_local\_clock \= Clock(depth\_local\_ts, 0) local\_clock \= Clock(local\_ts, 0) out\_t \= 0 out\_mid\_tick \= np.empty(len(local\_ts), np.float64) out\_bid\_qty \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_bid\_weighted \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_ask\_qty \= np.empty((len(local\_ts), len(depth\_range)), np.float64) out\_ask\_weighted \= np.empty((len(local\_ts), len(depth\_range)), np.float64) while local\_clock.ts <= end\_ts: ev \= select\_event(np.asarray(\[\ depth\_local\_clock.ts,\ local\_clock.ts\ \])) if ev \== \-1: raise ValueError elif ev \== 0: \# Builds the market depth. side \= depth\[depth\_local\_clock.rn\].side px\_tick \= round(depth\[depth\_local\_clock.rn\].px / tick\_size) \# Skips processing if the depth update price falls outside the defined range of interest. if px\_tick \> roi\_ub\_tick or px\_tick < roi\_lb\_tick: depth\_local\_clock.next() continue qty \= depth\[depth\_local\_clock.rn\].qty qty\_lot \= round(qty / lot\_size) if side \== 1: bid\_depth\[px\_tick \- roi\_lb\_tick\] \= qty if px\_tick < low\_bid\_tick: low\_bid\_tick \= px\_tick if px\_tick \> best\_bid\_tick and qty\_lot \> 0: \# Updates the best bid if the bid price is higher than the current best bid. best\_bid\_tick \= px\_tick if best\_bid\_tick \>= best\_ask\_tick: \# When the best bid is greater than or equal to the best ask, \# updates the best ask to the lowest ask above the new best bid. best\_ask\_tick \= depth\_above(ask\_depth, best\_bid\_tick + 1, high\_ask\_tick, roi\_lb\_tick) elif px\_tick \== best\_bid\_tick and qty\_lot \== 0: \# Finds the new best bid if the current best bid is deleted. best\_bid\_tick \= depth\_below(bid\_depth, px\_tick, low\_bid\_tick, roi\_lb\_tick) else: ask\_depth\[px\_tick \- roi\_lb\_tick\] \= qty if px\_tick \> high\_ask\_tick: high\_ask\_tick \= px\_tick if px\_tick < best\_ask\_tick and qty\_lot \> 0: \# Updates the best ask if the ask price is lower than the current best ask. best\_ask\_tick \= px\_tick if best\_ask\_tick <= best\_bid\_tick: \# When the best ask is less than or equal to the best bid, \# updates the best bid to the highest bid below the new best ask. best\_bid\_tick \= depth\_below(bid\_depth, best\_ask\_tick \- 1, low\_bid\_tick, roi\_lb\_tick) elif px\_tick \== best\_ask\_tick and qty\_lot \== 0: \# Finds the best ask if the current best ask is deleted. best\_ask\_tick \= depth\_above(ask\_depth, px\_tick, high\_ask\_tick, roi\_lb\_tick) depth\_local\_clock.next() elif ev \== 1: if best\_bid\_tick \== INVALID\_MIN or best\_ask\_tick \== INVALID\_MAX: mid\_tick \= np.nan out\_bid\_qty\[out\_t, :\] \= np.nan out\_bid\_weighted\[out\_t, :\] \= np.nan out\_ask\_qty\[out\_t, :\] \= np.nan out\_ask\_weighted\[out\_t, :\] \= np.nan else: mid\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2 \# Computes the order book imbalance for the depth range from the mid-price. \# Please see https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html # \# To compute order-book imbalance values, aggregate over depth levels d, where d \# denotes the tick distance from the mid price; d is symmetric from the mid tick. \# mid\_tick, Sum{Q\_bid\[d\]}, Sum{Q\_ask\[d\]}, Sum{d \* Q\_bid\[d\]}, Sum{d \* Q\_ask\[d\]} # \# VAMP = (Sum{P\_bid\[d\] \* Q\_ask\[d\]} + Sum{P\_ask\[d\] \* Q\_bid\[d\]}) / (Sum{Q\_bid\[d\]} + Sum{Q\_ask\[d\]}) \# = tick\_size \* (mid\_tick \* Sum{Q\_ask\[d\]} - Sum{d \* Q\_ask\[d\]} + mid\_tick \* Sum{Q\_bid\[d\]} + Sum{d \* Q\_bid\[d\]}) \# / (Sum{Q\_bid\[d\]} + Sum{Q\_ask\[d\]}) # \# Bid\_effective = Sum{P\_bid\[d\] \* Q\_bid\[d\]} / Sum{Q\_bid\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_bid\[d\]} - Sum{d \* Q\_bid\[d\]}) / Sum{Q\_bid\[d\]} \# Ask\_effective = Sum{P\_ask\[d\] \* Q\_ask\[d\]} / Sum{Q\_ask\[d\]} \# = tick\_size \* (mid\_tick \* Sum{Q\_ask\[d\]} + Sum{d \* Q\_ask\[d\]}) / Sum{Q\_ask\[d\]} i \= 0 bid\_qty \= 0.0 bid\_weighted \= 0.0 for d in range(best\_bid\_tick, low\_bid\_tick \- 1, \-1): bid\_qty += bid\_depth\[d \- roi\_lb\_tick\] bid\_weighted += bid\_depth\[d \- roi\_lb\_tick\] \* (mid\_tick \- d) if d < mid\_tick \* (1 \- depth\_range\[i\]): out\_bid\_qty\[out\_t, i\] \= bid\_qty out\_bid\_weighted\[out\_t, i\] \= bid\_weighted i += 1 if i \== len(depth\_range): break i \= 0 ask\_qty \= 0.0 ask\_weighted \= 0.0 for d in range(best\_ask\_tick, high\_ask\_tick + 1): ask\_qty += ask\_depth\[d \- roi\_lb\_tick\] ask\_weighted += ask\_depth\[d \- roi\_lb\_tick\] \* (d \- mid\_tick) if d \> mid\_tick \* (1 + depth\_range\[i\]): out\_ask\_qty\[out\_t, i\] \= ask\_qty out\_ask\_weighted\[out\_t, i\] \= ask\_weighted i += 1 if i \== len(depth\_range): break out\_mid\_tick\[out\_t\] \= mid\_tick out\_t += 1 local\_clock.next() return ( out\_mid\_tick\[:out\_t\], out\_bid\_qty\[:out\_t\], out\_bid\_weighted\[:out\_t\], out\_ask\_qty\[:out\_t\], out\_ask\_weighted\[:out\_t\] ) \[12\]: import gzip def load\_incremental\_book(file, buffer\_size\=500\_000\_000): depth\_dtype \= np.dtype(\[\ ('is\_snapshot', np.int32),\ ('side', np.int32),\ ('px', np.float64),\ ('qty', np.float64)\ \]) exch\_ts \= np.empty(buffer\_size, np.int64) local\_ts \= np.empty(buffer\_size, np.int64) data \= np.empty(buffer\_size, depth\_dtype) with gzip.open(file) as f: header \= True i \= 0 while True: line \= f.readline() if not line: break if header: header \= False continue columns \= line.decode().split(',') if i \== buffer\_size: raise MemoryError('Not enough buffer size to load data') exch\_ts\[i\] \= int(columns\[2\]) \* 1000 local\_ts\[i\] \= int(columns\[3\]) \* 1000 data\[i\]\[0\] \= 1 if columns\[4\] \== 'true' else 0 data\[i\]\[1\] \= 1 if columns\[5\] \== 'bid' else \-1 data\[i\]\[2\] \= float(columns\[6\]) data\[i\]\[3\] \= float(columns\[7\]) i += 1 return exch\_ts\[:i\], local\_ts\[:i\], data\[:i\] Computing the order book imbalance requires tick size, lot size, and both lower and upper bounds of market depth. We set these bounds relative to the mid price during the period. \[13\]: symbol\_dict \= { 'BTCUSDT': {'tick\_size': 0.1, 'lot\_size': 0.001}, 'ETHUSDT': {'tick\_size': 0.01, 'lot\_size': 0.001}, 'XRPUSDT': {'tick\_size': 0.0001, 'lot\_size': 0.1}, 'SOLUSDT': {'tick\_size': 0.01, 'lot\_size': 0.01}, 'DOGEUSDT': {'tick\_size': 0.00001, 'lot\_size': 1}, } \# roi\_lb and roi\_ub are set relative to the mid price. for i, symbol in enumerate(symbol\_dict.keys()): symbol\_dict\[symbol\]\['mid'\] \= df\_futures\[:, i\].mean() \[14\]: symbol\_dict \[14\]: {'BTCUSDT': {'tick\_size': 0.1, 'lot\_size': 0.001, 'mid': 114809.1865616898}, 'ETHUSDT': {'tick\_size': 0.01, 'lot\_size': 0.001, 'mid': 3615.6461275868055}, 'XRPUSDT': {'tick\_size': 0.0001, 'lot\_size': 0.1, 'mid': 2.981095579398148}, 'SOLUSDT': {'tick\_size': 0.01, 'lot\_size': 0.01, 'mid': 167.65471828877315}, 'DOGEUSDT': {'tick\_size': 1e-05, 'lot\_size': 1, 'mid': 0.2062502108564815}} \[15\]: def load\_depth(date, symbol\_dict): start \= date \= datetime.datetime.strptime(str(date), '%Y-%m-%d').replace(tzinfo\=datetime.timezone.utc) end \= start + datetime.timedelta(days\=1) \# 0.25%, 0.5%, 0.75%, 1%, 1.5%, 2.5% from the mid price. depth\_range \= np.asarray(\[0.0025, 0.005, 0.0075, 0.01, 0.015, 0.025\]) running\_interval \= 100\_000\_000 start\_ts \= int(start.timestamp() \* 1\_000\_000\_000) + running\_interval end\_ts \= int(end.timestamp() \* 1\_000\_000\_000) local\_ts \= np.arange(start\_ts, end\_ts + running\_interval, running\_interval) mid\_ \= \[\] bid\_q \= \[\] bid\_w \= \[\] ask\_q \= \[\] ask\_w \= \[\] for symbol, info in symbol\_dict.items(): depth \= load\_incremental\_book(f'futures/{symbol}\_incremental\_book\_L2\_{date.strftime("%Y%m%d")}.csv.gz') tick\_size \= info\['tick\_size'\] lot\_size \= info\['lot\_size'\] roi\_lb \= np.floor(info\['mid'\] \* 0.5 / tick\_size) \* tick\_size roi\_ub \= np.ceil(info\['mid'\] \* 1.5 / tick\_size) \* tick\_size print(symbol, roi\_lb, roi\_ub, tick\_size) ( out\_mid\_tick, out\_bid\_qty, out\_bid\_weighted, out\_ask\_qty, out\_ask\_weighted ) \= precompute\_obi( tick\_size, lot\_size, roi\_lb, roi\_ub, end\_ts, local\_ts, depth\[1\], depth\[2\], depth\_range ) mid\_.append(out\_mid\_tick\[:, np.newaxis\]) bid\_q.append(out\_bid\_qty\[:, np.newaxis, :\]) bid\_w.append(out\_bid\_weighted\[:, np.newaxis, :\]) ask\_q.append(out\_ask\_qty\[:, np.newaxis, :\]) ask\_w.append(out\_ask\_weighted\[:, np.newaxis, :\]) mid\_ \= np.concatenate(mid\_, axis\=1) bid\_q \= np.concatenate(bid\_q, axis\=1) bid\_w \= np.concatenate(bid\_w, axis\=1) ask\_q \= np.concatenate(ask\_q, axis\=1) ask\_w \= np.concatenate(ask\_w, axis\=1) return local\_ts, mid\_, bid\_q, bid\_w, ask\_q, ask\_w \[16\]: ts, mid\_tick, bid\_qty, bid\_weighted, ask\_qty, ask\_weighted \= load\_depth(date, symbol\_dict) To avoid look-ahead data leakage, which can lead to critical errors, the resampled timestamps must align with those of the other preprocessed data. \[17\]: ( pl.DataFrame({'local\_ts': ts}) .with\_columns( pl.col('local\_ts').cast(pl.Datetime('ns')) ) ) \[17\]: shape: (864\_000, 1) | local\_ts | | --- | | datetime\[ns\] | | --- | | 2025-08-01 00:00:00.100 | | 2025-08-01 00:00:00.200 | | 2025-08-01 00:00:00.300 | | 2025-08-01 00:00:00.400 | | 2025-08-01 00:00:00.500 | | … | | 2025-08-01 23:59:59.600 | | 2025-08-01 23:59:59.700 | | 2025-08-01 23:59:59.800 | | 2025-08-01 23:59:59.900 | | 2025-08-02 00:00:00 | The processed order book imbalance components are stored in a Parquet file. As Parquet supports only one-dimensional arrays, the data must be reshaped into a 1-D array prior to storage and reverted to its original form upon retrieval. \[18\]: import pyarrow as pa import pyarrow.parquet as pq table \= pa.Table.from\_pydict({ 'bid\_qty': bid\_qty.reshape(\-1), 'bid\_weighted': bid\_weighted.reshape(\-1), 'ask\_qty': ask\_qty.reshape(\-1), 'ask\_weighted': ask\_weighted.reshape(\-1) }) pq.write\_table(table, "obi.parquet", compression\='zstd') To revert a 1-D array back to its original matrix, the dimensions \[time, pair\] must be known. \[19\]: table \= pq.read\_table("obi.parquet") bid\_qty \= table\['bid\_qty'\].to\_numpy().reshape(len(ts), 5, \-1) bid\_weighted \= table\['bid\_weighted'\].to\_numpy().reshape(len(ts), 5, \-1) ask\_qty \= table\['ask\_qty'\].to\_numpy().reshape(len(ts), 5, \-1) ask\_weighted \= table\['ask\_weighted'\].to\_numpy().reshape(len(ts), 5, \-1) \# As shown in Section A, there is a slight difference between the mid-price \# from the Level-2 feed and the mid-price from the Level-1 feed. \# But, we use the Level-1 mid-price for implementation simplicity. tick\_size\_ \= np.asarray(\[val\['tick\_size'\] for val in symbol\_dict.values()\])\[np.newaxis, :\] mid\_tick \= df\_futures.to\_numpy() / tick\_size\_ vamp\_tick \= np.divide( ( mid\_tick\[:, :, np.newaxis\] \* ask\_qty \- ask\_weighted + mid\_tick\[:, :, np.newaxis\] \* bid\_qty + bid\_weighted ), bid\_qty + ask\_qty ) bid\_eff\_tick \= (mid\_tick\[:, :, np.newaxis\] \* bid\_qty \- bid\_weighted) / bid\_qty ask\_eff\_tick \= (mid\_tick\[:, :, np.newaxis\] \* ask\_qty + ask\_weighted) / ask\_qty vamp\_eff\_tick \= np.divide( bid\_eff\_tick \* ask\_qty + ask\_eff\_tick \* bid\_qty, bid\_qty + ask\_qty ) /tmp/ipykernel\_473491/2341654302.py:14: RuntimeWarning: invalid value encountered in divide vamp\_tick = np.divide( /tmp/ipykernel\_473491/2341654302.py:22: RuntimeWarning: invalid value encountered in divide bid\_eff\_tick = (mid\_tick\[:, :, np.newaxis\] \* bid\_qty - bid\_weighted) / bid\_qty /tmp/ipykernel\_473491/2341654302.py:23: RuntimeWarning: invalid value encountered in divide ask\_eff\_tick = (mid\_tick\[:, :, np.newaxis\] \* ask\_qty + ask\_weighted) / ask\_qty Price Return Signal[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Price-Return-Signal "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------- Now we are ready to express our alpha in a formulaic using this framework. Let’s go through this one by one. As discussed in the [Market Making with Alpha - APT](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20APT.html) tutorial, the most basic assumption is that the futures price return follows the underlying spot price return, i.e., Returnfutures\=Returnspot Which is: PricefairfuturesPricepastfutures−1\=PricecurrentspotPricepastspot−1 Since we have rewritten our fair price in return terms relative to the current mid price, we obtain: Pricefairfutures\=(1+Returnfair)×Pricecurrentfutures Returnfair\=Returnspot−Returnfutures1+Returnfutures When Returnfutures<<1, this can be approximated as Returnfair\=Returnspot−Returnfutures This can be interpreted as futures reverting to the spot in return terms, or as a lead-lag relationship. \[20\]: \# approximately 15-minute returns df\_fut\_returns\_15m \= df\_fut\_returns.with\_columns( pl.all().rolling\_sum(window\_size\=10 \* 60 \* 15) ) df\_usdt\_returns\_15m \= df\_usdt\_returns.with\_columns( pl.all().rolling\_sum(window\_size\=10 \* 60 \* 15) ) df\_fdusd\_returns\_15m \= df\_fdusd\_returns.with\_columns( pl.all().rolling\_sum(window\_size\=10 \* 60 \* 15) ) \[21\]: rev\_usdt \= df\_usdt\_returns\_15m.to\_numpy() \- df\_fut\_returns\_15m.to\_numpy() rev\_fdusd \= df\_fdusd\_returns\_15m.to\_numpy() \- df\_fut\_returns\_15m.to\_numpy() When considering cross-asset relationships, such as ETHUSDT impacting BTCUSDT or XRPUSDT influencing BTCUSDT, the relationship can be represented as a linear sum of the previous equation with asset-specific multipliers, denoted by β. Under the simplifying assumption that all β = 1, the model reduces to an equally weighted market, which can be formulated analogously to the previous case. \[22\]: df\_fut\_returns\_5m \= df\_fut\_returns.with\_columns( pl.all().rolling\_sum(window\_size\=10 \* 60 \* 5) ) This formula corresponds to the market reversion alpha commonly employed in statistical arbitrage. \[23\]: rev\_mkt \= np.mean(df\_fut\_returns\_5m.to\_numpy(), axis\=1)\[:, np.newaxis\] \- df\_fut\_returns\_5m.to\_numpy() In a more sophisticated approach, you can construct a custom weighted market based on peer group information, correlations, and estimated betas, which provides a better proxy for peer group reversion. Order Book Imbalance Signal[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Order-Book-Imbalance-Signal "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------- In our pricing framework, the fair price is represented in return terms, as shown below: Pricefairfutures\=(1+Returnfair)×Pricecurrentfutures While the order book imbalance does not strictly need to be represented in return terms, it must be scaled appropriately to align with returns. In addition, just like the spot reversion, it’s crucial to find the primary market since not all markets have the same position in terms of price driver. In the sense of microstructure, the very short-term movement can be driven by the individual market’s order book imbalance but in large, primary price discovery occurs in the primary market’s order book. So even if you trade in the small exchange, it could not help to look at the order book imbalance in the exchange, Rather, you need to look at the primary market’s order book. It also can be applied cross assets. The small altcoin can be driven more by the larger peer coin such as BTCUSDT as a market proxy or large coin in the same peer group than its own order book. You can find the a lot of different grouping based on qualitative research, but you can also make your own based on quantitative grouping. \[24\]: vamp\_returns \= vamp\_tick / mid\_tick\[:, :, np.newaxis\] \- 1 vamp\_eff\_returns \= vamp\_eff\_tick / mid\_tick\[:, :, np.newaxis\] \- 1 For the standardized simple order book imbalance, the scale must be aligned with returns, which requires an adjustment multiplier. The resulting signal is the same as the one introduced in the [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) tutorial. \[25\]: standardized\_obi \= \[\] for i in range(bid\_qty.shape\[2\]): df \= pl.DataFrame(bid\_qty\[:, :, i\] \- ask\_qty\[:, :, i\]) \# Standardization applied over a 1-hour window. df \= df.with\_columns( (pl.all() \- pl.all().rolling\_mean(window\_size\=10 \* 60 \* 60)) / pl.all().rolling\_std(window\_size\=10 \* 60 \* 60) ) standardized\_obi.append(df.to\_numpy()\[:, :, np.newaxis\]) \# To match the scale of the return term, the exact beta is multiplied again later. adj\_mul \= 0.0001 standardized\_obi \= np.concatenate(standardized\_obi, axis\=2) \* adj\_mul Accelerated Backtesting[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Accelerated-Backtesting "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------- For demonstration purposes—and to make it easy for you to run this tutorial yourself—we use only one day of data, given constraints on what can be provided. The parameters and signals, however, should be calibrated on longer-term data and will be presented later. Let’s backtest each signal individually, as well as their combination. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[26\]: record\_dtype \= np.dtype( \[\ ('timestamp', 'i8'),\ ('price', 'f8'),\ ('position', 'f8'),\ ('balance', 'f8'),\ ('fee', 'f8'),\ ('num\_trades', 'i8'),\ ('trading\_volume', 'f8'),\ ('trading\_value', 'f8')\ \], align\=True ) @njit def accelerated\_backtest( alpha, relative\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ): \# req\_bid\_tick: bid order price in ticks (limit buy order with GTX) sent to the exchange, before the exchange acknowledges it. \# req\_ask\_tick: ask order price in ticks (limit sell order with GTX) sent to the exchange, before the exchange acknowledges it. \# open\_bid\_tick: bid order price in ticks acknowledged by the exchange, currently an open order in the market. \# open\_ask\_tick: ask order price in ticks acknowledged by the exchange, currently an open order in the market. # \# INVALID\_MIN and INVALID\_MAX indicate that there are no orders. # \# Example: \# If req\_bid\_tick is INVALID\_MIN and there is an open bid order, \# the open bid order will be canceled (if the cancel request reaches the exchange before the order is filled). \# When an order is filled, its price is set to INVALID\_MIN or INVALID\_MAX accordingly. req\_bid\_tick \= open\_bid\_tick \= INVALID\_MIN req\_ask\_tick \= open\_ask\_tick \= INVALID\_MAX \# corresponding order quantities. open\_bid\_qty \= req\_bid\_qty \= 0.0 open\_ask\_qty \= req\_ask\_qty \= 0.0 \# Initial state. balance \= 0.0 position \= 0.0 num\_trades \= 0 trading\_value \= 0.0 trading\_volume \= 0.0 \# Row index iterator t \= 0 \# State record for stats rec\_i \= 0 record \= np.empty(len(local\_ts), record\_dtype) while True: #-------------------------------------------------------- \# Local bot logic at \`local\_ts\[t\]\`. mid\_tick \= (best\_bid\_tick\[t\] + best\_ask\_tick\[t\]) / 2.0 mid\_px \= mid\_tick \* tick\_size notional\_position\_value \= position \* mid\_px normalized\_position \= notional\_position\_value / max\_notional\_position relative\_bid\_depth \= relative\_half\_spread + skew \* normalized\_position relative\_ask\_depth \= relative\_half\_spread \- skew \* normalized\_position forecast\_tick \= (1 + alpha\[t\]) \* mid\_tick req\_bid\_tick \= min(np.floor(forecast\_tick \* (1.0 \- relative\_bid\_depth)), best\_bid\_tick\[t\]) req\_ask\_tick \= max(np.ceil(forecast\_tick \* (1.0 + relative\_ask\_depth)), best\_ask\_tick\[t\]) req\_bid\_qty \= req\_ask\_qty \= max(round(order\_notional\_value / mid\_px / lot\_size) \* lot\_size, lot\_size) \# If the position exceeds the risk limit (max notional position), \# no orders shall be open in that direction. if normalized\_position \> 1: req\_bid\_tick \= INVALID\_MIN if normalized\_position < \-1: req\_ask\_tick \= INVALID\_MAX #-------------------------------------------------------- \# Records the current state. record\[rec\_i\].timestamp \= local\_ts\[t\] record\[rec\_i\].price \= mid\_tick \* tick\_size record\[rec\_i\].position \= position record\[rec\_i\].balance \= balance \* tick\_size record\[rec\_i\].fee \= trading\_value \* tick\_size \* fee record\[rec\_i\].num\_trades \= num\_trades record\[rec\_i\].trading\_volume \= trading\_volume record\[rec\_i\].trading\_value \= trading\_value \* tick\_size rec\_i += 1 #-------------------------------------------------------- \# Processes the exchange-side logic (order fill logic). \# If any of the requested order prices differ from the open order's price, \# it is assumed that the bot sent the order request. \# The request will be acknowledged and processed at \`order\_ack\_ts\[t\]\`. \# Otherwise, check if the open order is filled. if req\_bid\_tick != open\_bid\_tick or req\_ask\_tick != open\_ask\_tick: \# The current time is \`order\_ack\_ts\[t\]\`. order\_ack\_ts\_ \= order\_ack\_ts\[t\] \# If there are open orders with valid prices, \# checks whether they are filled before accepting the newly requested orders. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# New orders are treated as GTX. \# If the requested buy order price is greater than or equal to the best ask, \# or the requested sell order price is less than or equal to the best bid, \# the orders are rejected. \# Invalidates the price if the order is rejected. if req\_bid\_tick \>= best\_ask\_tick\_ack\[t\]: req\_bid\_tick \= INVALID\_MIN if req\_ask\_tick <= best\_bid\_tick\_ack\[t\]: req\_ask\_tick \= INVALID\_MAX \# Updates the open orders to reflect accepted orders. open\_bid\_tick \= req\_bid\_tick open\_ask\_tick \= req\_ask\_tick open\_bid\_qty \= req\_bid\_qty open\_ask\_qty \= req\_ask\_qty \# If there are open orders with valid prices, \# checks whether they are filled before the next local timestamp (\`local\_ts\[t+n\]\`) \# that is greater than the current timestamp (\`order\_ack\_ts\[t\]\`). if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\_after\_ack\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX \# The next local timestamp must be greater than the current timestamp (\`order\_ack\_ts\[t\]\`). while t < len(local\_ts) and local\_ts\[t\] < order\_ack\_ts\_: t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break else: \# Checks if the open orders are filled between two local timestamps. \# The next row of data contains the bid fill price (in ticks) and ask fill price (in ticks) \# for that interval (step). t += 1 \# Breaks if no more rows remain for processing. if t \== len(local\_ts): break \# # If there are open orders with valid prices, checks if they are filled. if open\_bid\_tick \> INVALID\_MIN and open\_bid\_tick \>= bid\_fill\_tick\[t\]: execute\_value \= open\_bid\_tick \* open\_bid\_qty balance \-= execute\_value position += open\_bid\_qty num\_trades += 1 trading\_volume += open\_bid\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_bid\_tick \= INVALID\_MIN if open\_ask\_tick < INVALID\_MAX and open\_ask\_tick <= ask\_fill\_tick\[t\]: execute\_value \= open\_ask\_tick \* open\_ask\_qty balance += execute\_value position \-= open\_ask\_qty num\_trades += 1 trading\_volume += open\_ask\_qty trading\_value += execute\_value \# Invalidates the price because the order is filled. open\_ask\_tick \= INVALID\_MAX return record\[:rec\_i\] \[27\]: from hftbacktest.stats import LinearAssetRecord from IPython.display import display def backtest(symbol, alpha, rel\_half\_spread): \# Reads the preprocessed fill prices for the accelerated backtest. table \= pq.read\_table(f'data/{symbol}\_20250801.parquet') local\_ts \= table\['local\_ts'\].to\_numpy() best\_bid\_tick \= table\['best\_bid\_tick'\].to\_numpy() best\_ask\_tick \= table\['best\_ask\_tick'\].to\_numpy() bid\_fill\_tick \= table\['bid\_fill\_tick'\].to\_numpy() ask\_fill\_tick \= table\['ask\_fill\_tick'\].to\_numpy() order\_ack\_ts \= table\['order\_ack\_ts'\].to\_numpy() bid\_fill\_tick\_ack \= table\['bid\_fill\_tick\_ack'\].to\_numpy() ask\_fill\_tick\_ack \= table\['ask\_fill\_tick\_ack'\].to\_numpy() best\_bid\_tick\_ack \= table\['best\_bid\_tick\_ack'\].to\_numpy() best\_ask\_tick\_ack \= table\['best\_ask\_tick\_ack'\].to\_numpy() bid\_fill\_tick\_after\_ack \= table\['bid\_fill\_tick\_after\_ack'\].to\_numpy() ask\_fill\_tick\_after\_ack \= table\['ask\_fill\_tick\_after\_ack'\].to\_numpy() \# Sets the basic parameters skew \= rel\_half\_spread order\_notional\_value \= 50000 max\_notional\_position \= order\_notional\_value \* 20 fee\_per\_value \= \-0.00005 \# 0.005% rebates tick\_size \= symbol\_dict\[symbol\]\['tick\_size'\] lot\_size \= symbol\_dict\[symbol\]\['lot\_size'\] record \= accelerated\_backtest( alpha, rel\_half\_spread, skew, order\_notional\_value, max\_notional\_position, fee\_per\_value, tick\_size, lot\_size, local\_ts, best\_bid\_tick, best\_ask\_tick, bid\_fill\_tick, ask\_fill\_tick, order\_ack\_ts, bid\_fill\_tick\_ack, ask\_fill\_tick\_ack, best\_bid\_tick\_ack, best\_ask\_tick\_ack, bid\_fill\_tick\_after\_ack, ask\_fill\_tick\_after\_ack ) \# Prints the performance summary and plots the equity. stats \= ( LinearAssetRecord(record) .resample('1s') .stats(book\_size\=max\_notional\_position) ) display(stats.summary()) display(stats.plot()) To compare the performance of each signal, evaluate a zero-alpha, inventory-controlled market-making strategy. \[28\]: alpha \= pl.DataFrame(np.zeros\_like(rev\_usdt)) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 7.60934 | 10.894142 | 0.001196 | 0.003273 | 350.0 | 17.49908 | 0.365565 | 0.000068 | 347732.5629 | ![../_images/tutorials_Pricing_Framework_47_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_47_1.png) The backtest results for the BTCUSDT spot return alpha. \[29\]: alpha \= 1.0 \* rev\_usdt alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 28.52991 | 42.995793 | 0.007582 | 0.003411 | 971.0 | 48.550618 | 2.222404 | 0.000156 | 1.0488e6 | ![../_images/tutorials_Pricing_Framework_49_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_49_1.png) For BTCFDUSD spot, the performance is weaker than BTCUSDT spot. Note, however, that this is only a one-day demonstration; comprehensive validation requires testing over a longer period. \[30\]: alpha \= 1.0 \* rev\_fdusd alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 12.046548 | 17.388393 | 0.004711 | 0.007991 | 665.0 | 33.250376 | 0.589453 | 0.000142 | 1.0462e6 | ![../_images/tutorials_Pricing_Framework_51_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_51_1.png) A reversion to the equally weighted market, as shown below, does not appear to be effective when applied independently. \[31\]: alpha \= 1.0 \* rev\_mkt alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | \-21.303697 | \-29.843829 | \-0.024671 | 0.044545 | 8017.0 | 400.849028 | \-0.553835 | \-0.000062 | 1.0533e6 | ![../_images/tutorials_Pricing_Framework_53_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_53_1.png) Combining the two spot price return signals shows the better equity curve. \[32\]: alpha \= 1.0 \* rev\_usdt + 1.0 \* rev\_fdusd alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 23.227932 | 33.498231 | 0.012065 | 0.011553 | 1575.0 | 78.750637 | 1.044346 | 0.000153 | 1.0551e6 | ![../_images/tutorials_Pricing_Framework_55_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_55_1.png) VAMP at a depth range of 0.25% shows good performance. \[33\]: alpha \= 1.0 \* vamp\_returns\[:, :, 0\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 25.643825 | 36.901272 | 0.020504 | 0.00946 | 2297.0 | 114.853006 | 2.167534 | 0.000179 | 1.0995e6 | ![../_images/tutorials_Pricing_Framework_57_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_57_1.png) VAMP at a depth range of 0.5% shows slightly positive performance. \[34\]: alpha \= 1.0 \* vamp\_returns\[:, :, 1\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 7.024184 | 9.921883 | 0.008218 | 0.014378 | 1317.0 | 65.846376 | 0.571563 | 0.000125 | 1.0534e6 | ![../_images/tutorials_Pricing_Framework_59_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_59_1.png) In the case of Effective VAMP, the equity curve at a 0.5% depth range appears more stable than at a 0.25% depth range, in contrast to VAMP. Effective VAMP at a depth range 0.25% \[35\]: alpha \= 1.0 \* vamp\_eff\_returns\[:, :, 0\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 23.614107 | 34.202664 | 0.015358 | 0.008395 | 1449.0 | 72.452146 | 1.829365 | 0.000212 | 1.0604e6 | ![../_images/tutorials_Pricing_Framework_62_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_62_1.png) Effective VAMP at a depth range 0.50% \[36\]: alpha \= 1.0 \* vamp\_eff\_returns\[:, :, 1\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 18.078537 | 25.716361 | 0.017605 | 0.012803 | 1466.0 | 73.301925 | 1.375036 | 0.00024 | 1.0549e6 | ![../_images/tutorials_Pricing_Framework_64_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_64_1.png) The [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) tutorial covered the standardized simple order book imbalance, and its effectiveness is reconfirmed within the current pricing framework. The parameters of the standardized order book imbalance are the same as those used in the [Market Making with Alpha - Order Book Imbalance](https://hftbacktest.readthedocs.io/en/latest/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html) tutorial. \[37\]: alpha \= 1.0 \* standardized\_obi\[:, :, 4\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 21.421849 | 30.033065 | 0.015016 | 0.012204 | 1007.0 | 50.349009 | 1.230373 | 0.000298 | 1.0587e6 | ![../_images/tutorials_Pricing_Framework_66_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_66_1.png) The following illustrates the performance of the equally weighted linear sum of the signals. \[38\]: alpha \= (1.0 \* rev\_usdt + 1.0 \* rev\_fdusd + 1.0 \* vamp\_eff\_returns\[:, :, 1\] + 1.0 \* standardized\_obi\[:, :, 4\]) / 4 alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 24.180597 | 34.395776 | 0.014064 | 0.007173 | 671.0 | 33.550337 | 1.960829 | 0.000419 | 1.0494e6 | ![../_images/tutorials_Pricing_Framework_68_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_68_1.png) Optimizing the weights of signals may lead to better results, but the inclusion of multiple factors with adjusted weights inevitably heightens the risk of overfitting. In essence, pricing and forecasting amount to managing this risk, as the informational edge provided by each individual alpha is intrinsically limited and noisy. \[39\]: alpha \= 0.3 \* rev\_usdt + 0.4 \* rev\_fdusd + 0.15 \* vamp\_eff\_returns\[:, :, 1\] + 0.15 \* standardized\_obi\[:, :, 4\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 32.178054 | 46.365475 | 0.014301 | 0.005087 | 576.0 | 28.799652 | 2.811546 | 0.000497 | 1.0509e6 | ![../_images/tutorials_Pricing_Framework_70_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_70_1.png) Even the reversion to the equal market, which does not improve results as an individual alpha, enhances performance when combined with multiple factors. However, the question remains: is this genuine or merely overfitting? A longer validation period is required to determine this. \[40\]: alpha \= 0.1 \* rev\_mkt + 1.0 \* rev\_usdt + 1.0 \* rev\_fdusd + 0.5 \* vamp\_eff\_returns\[:, :, 1\] + 0.1 \* standardized\_obi\[:, :, 4\] alpha \= pl.DataFrame(alpha).fill\_nan(0) alpha.columns \= \['BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'SOLUSDT', 'DOGEUSDT'\] backtest('BTCUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.00025) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 49.861114 | 72.608402 | 0.039216 | 0.011771 | 2109.0 | 105.45063 | 3.331713 | 0.000372 | 1.0718e6 | ![../_images/tutorials_Pricing_Framework_72_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_72_1.png) An additional metric for assessing the signal is the information coefficient. As no explicit target forward return was defined, the IC is plotted over the forward return horizon. \[41\]: def ic(symbol, alpha): ic \= \[\] k\_ \= np.arange(1, 36000, 100) for k in k\_: df\_alpha \= alpha.with\_columns( (df\_futures\[symbol\].shift(\-k) / df\_futures\[symbol\] \- 1).alias("fwd\_ret") ) \# Drops last k rows (since they have no forward return) df\_alpha \= df\_alpha.drop\_nulls() \# Computes IC ic.append(df\_alpha.select( pl.corr(symbol, 'fwd\_ret') ).item()) plt.plot(k\_ \* 0.1 / 60, ic) plt.title('IC by Forward Return Horizon') plt.xlabel('Forward Return Horizon (min)') plt.ylabel('IC') plt.grid() \[42\]: ic('BTCUSDT', alpha) ![../_images/tutorials_Pricing_Framework_75_0.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_75_0.png) ### ETHUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#ETHUSDT "Link to this heading") \[43\]: backtest('ETHUSDT', alpha\['ETHUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 16.907416 | 24.036481 | 0.022305 | 0.015962 | 3129.0 | 156.449781 | 1.397339 | 0.000143 | 1.0525e6 | ![../_images/tutorials_Pricing_Framework_77_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_77_1.png) ### XRPUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#XRPUSDT "Link to this heading") \[44\]: backtest('XRPUSDT', alpha\['XRPUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 13.239982 | 19.143894 | 0.015689 | 0.020004 | 2619.0 | 130.950162 | 0.784284 | 0.00012 | 1.0512e6 | ![../_images/tutorials_Pricing_Framework_79_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_79_1.png) ### SOLUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#SOLUSDT "Link to this heading") \[45\]: backtest('SOLUSDT', alpha\['SOLUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 1.833742 | 2.591809 | 0.0025 | 0.021056 | 1921.0 | 96.049416 | 0.118718 | 0.000026 | 1.0528e6 | ![../_images/tutorials_Pricing_Framework_81_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_81_1.png) ### DOGEUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#DOGEUSDT "Link to this heading") \[46\]: backtest('DOGEUSDT', alpha\['DOGEUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | \-12.423814 | \-17.422319 | \-0.024179 | 0.064173 | 3559.0 | 177.949679 | \-0.376771 | \-0.000136 | 1.0568e6 | ![../_images/tutorials_Pricing_Framework_83_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_83_1.png) ### ETHUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#id1 "Link to this heading") \[47\]: backtest('ETHUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 21.51677 | 30.388456 | 0.024653 | 0.014365 | 1964.0 | 98.199312 | 1.716209 | 0.000251 | 1.0958e6 | ![../_images/tutorials_Pricing_Framework_85_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_85_1.png) ### XRPUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#id2 "Link to this heading") \[48\]: backtest('XRPUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 28.746574 | 40.69477 | 0.040694 | 0.017157 | 1721.0 | 86.04976 | 2.371859 | 0.000473 | 1.0526e6 | ![../_images/tutorials_Pricing_Framework_87_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_87_1.png) ### SOLUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#id3 "Link to this heading") \[49\]: backtest('SOLUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 24.081415 | 33.788855 | 0.029743 | 0.015525 | 1334.0 | 66.698639 | 1.915799 | 0.000446 | 1.0537e6 | ![../_images/tutorials_Pricing_Framework_89_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_89_1.png) ### DOGEUSDT[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#id4 "Link to this heading") \[50\]: backtest('DOGEUSDT', alpha\['BTCUSDT'\].to\_numpy(), 0.0005) shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-08-01 00:00:00 | 2025-08-02 00:00:00 | 19.840674 | 27.900083 | 0.033406 | 0.015131 | 2322.0 | 116.097934 | 2.207866 | 0.000288 | 1.0536e6 | ![../_images/tutorials_Pricing_Framework_91_1.png](https://hftbacktest.readthedocs.io/en/latest/_images/tutorials_Pricing_Framework_91_1.png) Backtesting over a longer period[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Pricing%20Framework.html#Backtesting-over-a-longer-period "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- to be continued… --- # Integrating Custom Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Integrating Custom Data * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Integrating%20Custom%20Data.ipynb.txt) * * * Integrating Custom Data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html#Integrating-Custom-Data "Link to this heading") =================================================================================================================================================================== By combining your custom data with the feed data (order book and trades), you can enhance your strategy while harnessing the full potential of hftbacktest. Accessing Spot Price[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Integrating%20Custom%20Data.html#Accessing-Spot-Price "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------- In this example, we’ll combine the spot BTCUSDT mid-price with the USDM-Futures BTCUSDT feed data. This will enable you to estimate the fair value price, taking the underlying price into consideration. The spot data is used only in the local-side, and thus, should come with a local timestamp. Following this, in your backtesting logic, your task is to identify the most recent data that predates the current timestamp. The raw spot feed is processed to create spot data, which includes both a local timestamp and the spot mid price. \[1\]: import numpy as np import gzip import json spot \= np.full((100\_000, 2), np.nan, np.float64) i \= 0 with gzip.open('spot/btcusdt\_20240809.gz', 'r') as f: while True: line \= f.readline() if line is None or line \== b'': break line \= line.decode().strip() local\_timestamp \= int(line\[:19\]) obj \= json.loads(line\[20:\]) if obj\['stream'\] \== 'btcusdt@bookTicker': data \= obj\['data'\] mid \= (float(data\['b'\]) + float(data\['a'\])) / 2.0 spot\[i\] \= \[local\_timestamp, mid\] i += 1 spot \= spot\[:i\] It displays the basis and spot mid price as it identifies the latest Point-in-Time data that falls before the current timestamp. \[2\]: from numba import njit from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest out\_dtype \= np.dtype(\[('timestamp', 'i8'), ('mid\_price', 'f8'), ('spot\_mid\_price', 'f8')\]) @njit def print\_basis(hbt, spot): out \= np.empty(1\_000\_000, out\_dtype) t \= 0 spot\_row \= 0 \# Checks every 60-sec (in nanoseconds) while hbt.elapse(1\_000\_000\_000) \== 0: \# Finds the latest spot mid value. while spot\_row < len(spot) and spot\[spot\_row, 0\] <= hbt.current\_timestamp: spot\_row += 1 spot\_mid\_price \= spot\[spot\_row \- 1, 1\] if spot\_row \> 0 else np.nan depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 basis \= mid\_price \- spot\_mid\_price if t % 10 \== 0: print( 'current\_timestamp:', hbt.current\_timestamp, 'futures\_mid:', round(mid\_price, 2), ', spot\_mid:', round(spot\_mid\_price, 2), ', basis:', round(basis, 2) ) out\[t\].timestamp \= hbt.current\_timestamp out\[t\].mid\_price \= mid\_price out\[t\].spot\_mid\_price \= spot\_mid\_price t += 1 return out\[:t\] asset \= ( BacktestAsset() .data(\['usdm/btcusdt\_20240809.npz'\]) .initial\_snapshot('usdm/btcusdt\_20240808\_eod.npz') .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) out \= print\_basis(hbt, spot) \_ \= hbt.close() current\_timestamp: 1723161602500000000 futures\_mid: 61659.85 , spot\_mid: 61688.0 , basis: -28.14 current\_timestamp: 1723161612500000000 futures\_mid: 61713.95 , spot\_mid: 61727.8 , basis: -13.85 current\_timestamp: 1723161622500000000 futures\_mid: 61713.45 , spot\_mid: 61728.94 , basis: -15.5 current\_timestamp: 1723161632500000000 futures\_mid: 61666.05 , spot\_mid: 61690.08 , basis: -24.02 current\_timestamp: 1723161642500000000 futures\_mid: 61638.45 , spot\_mid: 61661.5 , basis: -23.06 current\_timestamp: 1723161652500000000 futures\_mid: 61632.05 , spot\_mid: 61663.98 , basis: -31.93 current\_timestamp: 1723161662500000000 futures\_mid: 61578.15 , spot\_mid: 61600.0 , basis: -21.85 current\_timestamp: 1723161672500000000 futures\_mid: 61524.25 , spot\_mid: 61562.0 , basis: -37.74 current\_timestamp: 1723161682500000000 futures\_mid: 61552.45 , spot\_mid: 61570.0 , basis: -17.54 current\_timestamp: 1723161692500000000 futures\_mid: 61593.05 , spot\_mid: 61606.0 , basis: -12.96 current\_timestamp: 1723161702500000000 futures\_mid: 61587.45 , spot\_mid: 61608.0 , basis: -20.54 current\_timestamp: 1723161712500000000 futures\_mid: 61561.15 , spot\_mid: 61589.88 , basis: -28.73 current\_timestamp: 1723161722500000000 futures\_mid: 61589.95 , spot\_mid: 61614.08 , basis: -24.14 current\_timestamp: 1723161732500000000 futures\_mid: 61608.95 , spot\_mid: 61632.13 , basis: -23.18 current\_timestamp: 1723161742500000000 futures\_mid: 61653.45 , spot\_mid: 61681.74 , basis: -28.29 current\_timestamp: 1723161752500000000 futures\_mid: 61673.45 , spot\_mid: 61700.0 , basis: -26.54 current\_timestamp: 1723161762500000000 futures\_mid: 61663.95 , spot\_mid: 61683.84 , basis: -19.89 current\_timestamp: 1723161772500000000 futures\_mid: 61640.85 , spot\_mid: 61664.0 , basis: -23.15 current\_timestamp: 1723161782500000000 futures\_mid: 61634.15 , spot\_mid: 61654.0 , basis: -19.85 current\_timestamp: 1723161792500000000 futures\_mid: 61618.05 , spot\_mid: 61666.0 , basis: -47.94 current\_timestamp: 1723161802500000000 futures\_mid: 61626.65 , spot\_mid: 61648.34 , basis: -21.69 current\_timestamp: 1723161812500000000 futures\_mid: 61586.25 , spot\_mid: 61612.0 , basis: -25.74 current\_timestamp: 1723161822500000000 futures\_mid: 61624.65 , spot\_mid: 61649.98 , basis: -25.33 current\_timestamp: 1723161832500000000 futures\_mid: 61611.55 , spot\_mid: 61644.0 , basis: -32.46 current\_timestamp: 1723161842500000000 futures\_mid: 61633.95 , spot\_mid: 61658.4 , basis: -24.46 current\_timestamp: 1723161852500000000 futures\_mid: 61635.95 , spot\_mid: 61656.02 , basis: -20.07 current\_timestamp: 1723161862500000000 futures\_mid: 61671.45 , spot\_mid: 61689.92 , basis: -18.47 current\_timestamp: 1723161872500000000 futures\_mid: 61651.55 , spot\_mid: 61664.0 , basis: -12.46 current\_timestamp: 1723161882500000000 futures\_mid: 61614.15 , spot\_mid: 61640.0 , basis: -25.84 current\_timestamp: 1723161892500000000 futures\_mid: 61605.95 , spot\_mid: 61622.12 , basis: -16.18 current\_timestamp: 1723161902500000000 futures\_mid: 61583.95 , spot\_mid: 61607.98 , basis: -24.04 \[3\]: import polars as pl import holoviews as hv df \= pl.DataFrame(out).with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns').alias('timestamp') ) hv.extension('bokeh') df.plot(x\='timestamp') ![]() ![]() \[3\]: Although this is a short-period sample, you can observe that the basis is mean-reverting. There may be statistical arbitrage opportunities, particularly if you are eligible for rebates or zero fees. \[4\]: ((df\['mid\_price'\] \- df\['spot\_mid\_price'\]) / df\['mid\_price'\] \* 10000).alias('basis bp').plot(x\='timestamp') \[4\]: --- # Getting Started — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Getting Started * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/Getting%20Started.ipynb.txt) * * * Getting Started[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Getting-Started "Link to this heading") ========================================================================================================================================= Printing the best bid and the best ask[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Printing-the-best-bid-and-the-best-ask "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: from numba import njit import numpy as np \# numba.njit is strongly recommended for fast backtesting. @njit def print\_bbo(hbt): \# Iterating until hftbacktest reaches the end of data. \# Elapses 60-sec every iteration. \# Time unit is the same as data's timestamp's unit. \# Timestamp of the sample data is in nanoseconds. while hbt.elapse(60 \* 1e9) \== 0: \# Gets the market depth for the first asset. depth \= hbt.depth(0) \# Prints the best bid and the best offer. print( 'current\_timestamp:', hbt.current\_timestamp, ', best\_bid:', np.round(depth.best\_bid, 1), ', best\_ask:', np.round(depth.best\_ask, 1) ) return True \[2\]: from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest asset \= ( BacktestAsset() \# Sets the data to feed for this asset. # \# Due to the vast size of tick-by-tick market depth and trade data, \# loading the entire dataset into memory can be challenging, \# particularly when backtesting across multiple days. \# HftBacktest offers lazy loading support and is compatible with npy and preferably npz. # \# For details on the normalized feed data, refer to the following documents. \# \* https://hftbacktest.readthedocs.io/en/latest/data.html \# \* https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html .data(\['usdm/btcusdt\_20240809.npz'\]) \# Sets the initial snapshot (optional). .initial\_snapshot('usdm/btcusdt\_20240808\_eod.npz') \# Asset type: \# \* Linear \# \* Inverse. \# 1.0 represents the contract size, which is the value of the asset per quoted price. .linear\_asset(1.0) \# HftBacktest provides two built-in latency models. \# \* constant\_latency \# \* intp\_order\_latency \# To implement your own latency model, please use Rust. # \# Time unit is the same as data's timestamp's unit. Timestamp of the sample data is in nanoseconds. \# Sets the order entry latency and response latency to 10ms. .constant\_latency(10\_000\_000, 10\_000\_000) \# HftBacktest provides several types of built-in queue position models. \# Please find the details in the documents below. \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Probability%20Queue%20Models.html # \# To implement your own queue position model, please use Rust. .risk\_adverse\_queue\_model() \# HftBacktest provides two built-in exchange models. \# \* no\_partial\_fill\_exchange \# \* partial\_fill\_exchange \# To implement your own exchange model, please use Rust. .no\_partial\_fill\_exchange() \# HftBacktest provides several built-in fee models. \# \* trading\_value\_fee\_model \# \* trading\_qty\_fee\_model \# \* flat\_per\_trade\_fee\_model # \# 0.02% maker fee and 0.07% taker fee. If the fee is negative, it represents a rebate. \# For example, -0.00005 represents a 0.005% rebate for the maker order. .trading\_value\_fee\_model(0.0002, 0.0007) \# Tick size of this asset: minimum price increasement .tick\_size(0.1) \# Lot size of this asset: minimum trading unit. .lot\_size(0.001) \# Sets the capacity of the vector that stores trades occurring in the market. \# If you set the size, you need call \`clear\_last\_trades\` to clear the vector. \# A value of 0 indicates that no market trades are stored. (Default) .last\_trades\_capacity(0) ) \# HftBacktest provides several types of built-in market depth implementations. \# HashMapMarketDepthBacktest constructs a Backtest using a HashMap-based market depth implementation. \# Another useful implementation is ROIVectorMarketDepth, which is utilized in ROIVectorMarketDepthBacktest. \# Please find the details in the document below. hbt \= HashMapMarketDepthBacktest(\[asset\]) You can see the best bid and best ask every 60 seconds. Since the price is a 32-bit float, there may be floating-point errors. Be careful when using it. In the example, for readability, the price is rounded based on the tick size. \[3\]: print\_bbo(hbt) current\_timestamp: 1723161661500000000 , best\_bid: 61594.1 , best\_ask: 61594.2 current\_timestamp: 1723161721500000000 , best\_bid: 61576.5 , best\_ask: 61576.6 current\_timestamp: 1723161781500000000 , best\_bid: 61629.6 , best\_ask: 61629.7 current\_timestamp: 1723161841500000000 , best\_bid: 61621.5 , best\_ask: 61621.6 current\_timestamp: 1723161901500000000 , best\_bid: 61583.9 , best\_ask: 61584.0 \[3\]: True HftBacktest cannot be reused. Therefore, after using the backtest, make sure to close it. If you use the backtest after closing, it will crash. \[4\]: \_ \= hbt.close() Feeding the data[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Feeding-the-data "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------- When you possess adequate memory, preloading the data into memory and providing it as input will be more efficient than lazy-loading during repeated backtesting. \[5\]: btcusdt\_20230809 \= np.load('usdm/btcusdt\_20240809.npz')\['data'\] btcusdt\_20230808\_eod \= np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\] asset \= ( BacktestAsset() .data(\[btcusdt\_20230809\]) .initial\_snapshot(btcusdt\_20230808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) \[6\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_bbo(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , best\_bid: 61594.1 , best\_ask: 61594.2 current\_timestamp: 1723161721500000000 , best\_bid: 61576.5 , best\_ask: 61576.6 current\_timestamp: 1723161781500000000 , best\_bid: 61629.6 , best\_ask: 61629.7 current\_timestamp: 1723161841500000000 , best\_bid: 61621.5 , best\_ask: 61621.6 current\_timestamp: 1723161901500000000 , best\_bid: 61583.9 , best\_ask: 61584.0 Getting the market depth[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Getting-the-market-depth "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def print\_3depth(hbt): while hbt.elapse(60 \* 1e9) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = price / tick\_size i \= 0 for tick\_price in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): qty \= depth.ask\_qty\_at\_tick(tick\_price) if qty \> 0: print( 'ask: ', qty, '@', np.round(tick\_price \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for tick\_price in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): qty \= depth.bid\_qty\_at\_tick(tick\_price) if qty \> 0: print( 'bid: ', qty, '@', np.round(tick\_price \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[8\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_3depth(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Submitting an order[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Submitting-an-order "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------- \[9\]: from hftbacktest import LIMIT, GTC, NONE, NEW, FILLED, CANCELED, EXPIRED @njit def print\_orders(hbt): \# You can access open orders and also closed orders via hbt.orders. \# Gets the OrderDict for the first asset. orders \= hbt.orders(0) \# hbt.orders is a dictionary, but be aware that it does not support all dict methods, and its keys are order\_id (int). order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' \# Exchange hasn't received an order yet. elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order.order\_id, ', order\_price:', np.round(order.price, 1), ', order\_qty:', order.qty, ', order\_status:', order\_status, ', order\_req:', order\_req ) @njit def submit\_order(hbt): is\_order\_submitted \= False while hbt.elapse(30 \* 1e9) \== 0: \# Prints open orders. print\_orders(hbt) depth \= hbt.depth(0) if not is\_order\_submitted: \# Submits a buy order at 300 ticks below the best bid for the first asset. order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC \# Good 'till cancel order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True return True \[10\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161691500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161751500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161781500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161811500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161841500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161871500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161901500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE Clearing inactive orders (FILLED, CANCELED, EXPIRED)[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Clearing-inactive-orders-(FILLED,-CANCELED,-EXPIRED) "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[11\]: from hftbacktest import GTC @njit def clear\_inactive\_orders(hbt): is\_order\_submitted \= False while hbt.elapse(30 \* 1e9) \== 0: print\_orders(hbt) \# Removes inactive(FILLED, CANCELED, EXPIRED) orders from hbt.orders for the first asset. hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True return True \[12\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) clear\_inactive\_orders(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE Watching a order status - pending due to order latency[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Watching-a-order-status---pending-due-to-order-latency "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[13\]: from hftbacktest import GTC @njit def watch\_pending(hbt): is\_order\_submitted \= False \# Elapses 0.01-sec every iteration. while hbt.elapse(0.01 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True \# Prevents too many prints orders \= hbt.orders(0) order \= orders.get(order\_id) if order.status \== NEW: return False return True The `order_status` is `None` until the acceptance message is received. \[14\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) watch\_pending(hbt) \_ \= hbt.close() current\_timestamp: 1723161601520000000 , order\_id: 1 , order\_price: 61629.7 , order\_qty: 1.0 , order\_status: NONE , order\_req: NEW current\_timestamp: 1723161601530000000 , order\_id: 1 , order\_price: 61629.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE Waiting for an order response[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Waiting-for-an-order-response "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[15\]: from hftbacktest import GTC @njit def wait\_for\_order\_response(hbt): order\_id \= 0 is\_order\_submitted \= False while hbt.elapse(0.01 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Prevents too many prints orders \= hbt.orders(0) if order\_id in orders: if orders.get(order\_id).status \== NEW: return False depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Waits for the order response for a given order id for the first asset. print('an order is submitted at', hbt.current\_timestamp) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1 \* 1e9) print('an order response is received at', hbt.current\_timestamp) is\_order\_submitted \= True return True Since the `ConstantLatency` model is used, the round-trip latency is exactly 200ms. Ideally, using historical order latency data collected from the live market is the best approach. However, if this data is not available, starting with artificially generated order latency based on feed latency is another option. We will explore this in the following examples. \[16\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) wait\_for\_order\_response(hbt) \_ \= hbt.close() an order is submitted at 1723161601510000000 an order response is received at 1723161601530000000 current\_timestamp: 1723161601540000000 , order\_id: 1 , order\_price: 61659.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE Printing position, balance, fee, and equity[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Printing-position,-balance,-fee,-and-equity "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[17\]: @njit def position(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Prints position print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', hbt.state\_values(0).balance, ', fee:', hbt.state\_values(0).fee ) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True return True \[18\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) position(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161781520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161841520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161901520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 Canceling an open order[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Canceling-an-open-order "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------- \[19\]: @njit def submit\_and\_cancel\_order(hbt): is\_order\_submitted \= False while hbt.elapse(0.1 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Cancels if there is an open order orders \= hbt.orders(0) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() \# an order is only cancellable if order status is NEW. \# cancel request is negated if the order is already filled or filled before cancel request is processed. if order.cancellable: hbt.cancel(0, order.order\_id, False) \# You can see status still NEW and see req CANCEL. print\_orders(hbt) \# cancels request also has order entry/response latencies the same as submitting. hbt.wait\_order\_response(0, order.order\_id, 1e9) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 order\_price \= depth.best\_bid \- 100 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True else: if len(hbt.orders(0)) \== 0: return False return True \[20\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_and\_cancel\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161601720000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE current\_timestamp: 1723161601720000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: CANCEL current\_timestamp: 1723161601840000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: CANCELED , order\_req: NONE Market order[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Market-order "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- \[21\]: from hftbacktest import MARKET @njit def print\_orders\_exec\_price(hbt): orders \= hbt.orders(0) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order.order\_id, ', order\_price:', np.round(order.price, 1), ', order\_qty:', order.qty, ', order\_status:', order\_status, ', exec\_price:', np.round(order.exec\_price, 1) ) @njit def market\_order(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) state\_values \= hbt.state\_values(0) print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', state\_values.balance, ', fee:', state\_values.fee ) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 \# Sets an arbitrary price, which does not affect MARKET orders. order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= MARKET hbt.submit\_sell\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) hbt.wait\_order\_response(0, order\_id, 1e9) \# You can see the order immediately filled. \# Also you can see the order executed at the best bid which is different from what it was submitted at. print('best\_bid:', depth.best\_bid) print\_orders\_exec\_price(hbt) is\_order\_submitted \= True return True \[22\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) market\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 best\_bid: 61594.100000000006 current\_timestamp: 1723161661520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , exec\_price: 61594.1 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161781520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161841520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161901520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 GTX, Post-Only order[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#GTX,-Post-Only-order "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------- \[23\]: from hftbacktest import GTX @njit def submit\_gtx(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) state\_values \= hbt.state\_values(0) print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', state\_values.balance, ', fee:', state\_values.fee ) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 \# Sets a deep price in the opposite side and it will be rejected by GTX. order\_price \= depth.best\_bid \- 100 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_sell\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True return True \[24\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_gtx(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61584.1 , order\_qty: 1.0 , order\_status: EXPIRED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161781520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161841520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161901520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 Plotting BBO[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Plotting-BBO "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- \[25\]: @njit def plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask): while hbt.elapse(1 \* 1e9) \== 0: \# Records data points local\_timestamp.append(hbt.current\_timestamp) depth \= hbt.depth(0) best\_bid.append(depth.best\_bid) best\_ask.append(depth.best\_ask) return True \[26\]: \# Uses Numba list for njit. from numba.typed import List from numba import int64, float64 import polars as pl local\_timestamp \= List.empty\_list(int64, allocated\=10000) best\_bid \= List.empty\_list(float64, allocated\=10000) best\_ask \= List.empty\_list(float64, allocated\=10000) hbt \= HashMapMarketDepthBacktest(\[asset\]) plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask) hbt.close() df \= pl.DataFrame({'timestamp': local\_timestamp, 'best\_bid': best\_bid, 'best\_ask': best\_ask}) df \= df.with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns') ) df.plot(x\='timestamp') \[26\]: Printing stats[](https://hftbacktest.readthedocs.io/en/latest/tutorials/Getting%20Started.html#Printing-stats "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------- \[27\]: @njit def submit\_order\_stats(hbt, recorder): buy\_order\_id \= 1 sell\_order\_id \= 2 half\_spread \= 5 \* hbt.depth(0).tick\_size while hbt.elapse(1 \* 1e9) \== 0: hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 if buy\_order\_id not in hbt.orders(0): order\_price \= round((mid\_price \- half\_spread) / depth.tick\_size) \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_buy\_order(0, buy\_order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) else: hbt.cancel(0, buy\_order\_id, False) if sell\_order\_id not in hbt.orders(0): order\_price \= round((mid\_price + half\_spread) / depth.tick\_size) \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_sell\_order(0, sell\_order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) else: hbt.cancel(0, sell\_order\_id, False) recorder.record(hbt) return True \[28\]: from hftbacktest import Recorder hbt \= HashMapMarketDepthBacktest(\[asset\]) recorder \= Recorder( \# The number of assets hbt.num\_assets, \# The buffer size for records 1000000 ) submit\_order\_stats(hbt, recorder.recorder) \_ \= hbt.close() You can get recorded states using the `get` method with the asset number. \[29\]: recorder.get(0) \[29\]: array(\[(1723161602500000000, 61659.85, 0., 0.000000e+00, 0. , 0, 0., 0. ),\ (1723161603500000000, 61659.95, 1., -6.165940e+04, 12.33188, 1, 1., 61659.4),\ (1723161604500000000, 61670.85, 1., -6.165940e+04, 12.33188, 1, 1., 61659.4),\ (1723161605500000000, 61692.45, 0., 1.200000e+01, 24.66616, 2, 2., 123330.8),\ (1723161606500000000, 61693.95, 0., 1.300000e+01, 49.34312, 4, 4., 246715.6),\ (1723161607500000000, 61695.45, -1., 6.170740e+04, 61.682 , 5, 5., 308410. ),\ (1723161608500000000, 61709.95, -2., 1.234033e+05, 74.02118, 6, 6., 370105.9),\ (1723161609500000000, 61707.35, -1., 6.169390e+04, 86.36306, 7, 7., 431815.3),\ (1723161610500000000, 61715.85, -1., 6.169390e+04, 86.36306, 7, 7., 431815.3),\ (1723161611500000000, 61711.85, -2., 1.234103e+05, 98.70634, 8, 8., 493531.7),\ (1723161612500000000, 61713.95, -3., 1.851227e+05, 111.04882, 9, 9., 555244.1),\ (1723161613500000000, 61706.15, -4., 2.468371e+05, 123.3917 , 10, 10., 616958.5),\ (1723161614500000000, 61708.25, -5., 3.085437e+05, 135.73302, 11, 11., 678665.1),\ (1723161615500000000, 61699.75, -6., 3.702525e+05, 148.07478, 12, 12., 740373.9),\ (1723161616500000000, 61700.95, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161617500000000, 61698.05, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161618500000000, 61706.95, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161619500000000, 61695.85, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161620500000000, 61713.45, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161621500000000, 61707.65, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161622500000000, 61713.45, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161623500000000, 61704.05, -6., 3.702455e+05, 172.75626, 14, 14., 863781.3),\ (1723161624500000000, 61702.45, -5., 3.085419e+05, 185.09698, 15, 15., 925484.9),\ (1723161625500000000, 61704.65, -6., 3.702448e+05, 197.43756, 16, 16., 987187.8),\ (1723161626500000000, 61704.65, -6., 3.702448e+05, 197.43756, 16, 16., 987187.8),\ (1723161627500000000, 61695.35, -5., 3.085406e+05, 209.7784 , 17, 17., 1048892. ),\ (1723161628500000000, 61693.75, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161629500000000, 61693.75, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161630500000000, 61682.35, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161631500000000, 61673.85, -3., 1.851640e+05, 234.45372, 19, 19., 1172268.6),\ (1723161632500000000, 61666.05, -2., 1.234906e+05, 246.7884 , 20, 20., 1233942. ),\ (1723161633500000000, 61671.05, -2., 1.234906e+05, 246.7884 , 20, 20., 1233942. ),\ (1723161634500000000, 61673.75, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161635500000000, 61673.75, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161636500000000, 61666.05, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161637500000000, 61670.45, -4., 2.468288e+05, 271.45604, 22, 22., 1357280.2),\ (1723161638500000000, 61664.05, -4., 2.468288e+05, 271.45604, 22, 22., 1357280.2),\ (1723161639500000000, 61649.05, -3., 1.851652e+05, 283.78876, 23, 23., 1418943.8),\ (1723161640500000000, 61645.05, -3., 1.851652e+05, 283.78876, 23, 23., 1418943.8),\ (1723161641500000000, 61640.05, -2., 1.235206e+05, 296.11768, 24, 24., 1480588.4),\ (1723161642500000000, 61638.45, -1., 6.188100e+04, 308.4456 , 25, 25., 1542228. ),\ (1723161643500000000, 61636.05, 0., 2.431000e+02, 320.77318, 26, 26., 1603865.9),\ (1723161644500000000, 61641.95, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161645500000000, 61641.95, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161646500000000, 61644.35, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161647500000000, 61636.45, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161648500000000, 61630.05, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161649500000000, 61630.05, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161650500000000, 61631.65, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161651500000000, 61639.05, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161652500000000, 61632.05, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161653500000000, 61633.95, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161654500000000, 61632.05, -2., 1.235104e+05, 370.081 , 30, 30., 1850405. ),\ (1723161655500000000, 61604.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161656500000000, 61604.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161657500000000, 61607.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161658500000000, 61603.15, 0., 2.722000e+02, 394.72864, 32, 32., 1973643.2),\ (1723161659500000000, 61601.15, 1., -6.133040e+04, 407.04916, 33, 33., 2035245.8),\ (1723161660500000000, 61595.35, 2., -1.229310e+05, 419.36928, 34, 34., 2096846.4),\ (1723161661500000000, 61594.15, 3., -1.845258e+05, 431.68824, 35, 35., 2158441.2),\ (1723161662500000000, 61578.15, 4., -2.461194e+05, 444.00696, 36, 36., 2220034.8),\ (1723161663500000000, 61565.25, 5., -3.076970e+05, 456.32248, 37, 37., 2281612.4),\ (1723161664500000000, 61563.65, 5., -3.076960e+05, 480.9486 , 39, 39., 2404743. ),\ (1723161665500000000, 61555.05, 6., -3.692592e+05, 493.26124, 40, 40., 2466306.2),\ (1723161666500000000, 61530.85, 7., -4.308138e+05, 505.57216, 41, 41., 2527860.8),\ (1723161667500000000, 61522.25, 8., -4.923442e+05, 517.87824, 42, 42., 2589391.2),\ (1723161668500000000, 61543. , 7., -4.308214e+05, 530.1828 , 43, 43., 2650914. ),\ (1723161669500000000, 61528.05, 7., -4.308214e+05, 530.1828 , 43, 43., 2650914. ),\ (1723161670500000000, 61539.85, 8., -4.923490e+05, 542.48832, 44, 44., 2712441.6),\ (1723161671500000000, 61524.15, 9., -5.538884e+05, 554.7962 , 45, 45., 2773981. ),\ (1723161672500000000, 61524.25, 9., -5.538884e+05, 554.7962 , 45, 45., 2773981. ),\ (1723161673500000000, 61535.95, 8., -4.923636e+05, 567.10116, 46, 46., 2835505.8),\ (1723161674500000000, 61531.45, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161675500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161676500000000, 61536.95, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161677500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161678500000000, 61534.75, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161679500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161680500000000, 61538.05, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161681500000000, 61549.25, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161682500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161683500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161684500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161685500000000, 61565.95, 7., -4.307963e+05, 604.02878, 49, 49., 3020143.9),\ (1723161686500000000, 61574.45, 6., -3.692299e+05, 616.34206, 50, 50., 3081710.3),\ (1723161687500000000, 61587.55, 6., -3.692299e+05, 616.34206, 50, 50., 3081710.3),\ (1723161688500000000, 61592.95, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161689500000000, 61592.95, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161690500000000, 61594.15, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161691500000000, 61598.95, 4., -2.460473e+05, 640.97858, 52, 52., 3204892.9),\ (1723161692500000000, 61593.05, 3., -1.844479e+05, 653.29846, 53, 53., 3266492.3),\ (1723161693500000000, 61582.55, 3., -1.844479e+05, 653.29846, 53, 53., 3266492.3),\ (1723161694500000000, 61582.55, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161695500000000, 61582.55, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161696500000000, 61587.15, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161697500000000, 61587.15, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161698500000000, 61588.75, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161699500000000, 61586.75, 4., -2.460289e+05, 690.25034, 56, 56., 3451251.7),\ (1723161700500000000, 61582.05, 5., -3.076151e+05, 702.56758, 57, 57., 3512837.9),\ (1723161701500000000, 61572.05, 6., -3.691967e+05, 714.8839 , 58, 58., 3574419.5),\ (1723161702500000000, 61587.45, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161703500000000, 61577.95, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161704500000000, 61582.05, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161705500000000, 61572.05, 5., -3.076189e+05, 751.83042, 61, 61., 3759152.1),\ (1723161706500000000, 61574.05, 4., -2.460463e+05, 764.14494, 62, 62., 3820724.7),\ (1723161707500000000, 61574.05, 4., -2.460463e+05, 764.14494, 62, 62., 3820724.7),\ (1723161708500000000, 61576.05, 3., -1.844717e+05, 776.45986, 63, 63., 3882299.3),\ (1723161709500000000, 61577.55, 2., -1.228951e+05, 788.77518, 64, 64., 3943875.9),\ (1723161710500000000, 61581.95, 1., -6.131710e+04, 801.09078, 65, 65., 4005453.9),\ (1723161711500000000, 61565.65, 1., -6.131710e+04, 801.09078, 65, 65., 4005453.9),\ (1723161712500000000, 61561.15, 2., -1.228823e+05, 813.40382, 66, 66., 4067019.1),\ (1723161713500000000, 61570.45, 2., -1.228813e+05, 838.02826, 68, 68., 4190141.3),\ (1723161714500000000, 61572.45, 1., -6.131040e+04, 850.34244, 69, 69., 4251712.2),\ (1723161715500000000, 61565.65, 1., -6.131040e+04, 850.34244, 69, 69., 4251712.2),\ (1723161716500000000, 61561.95, 2., -1.228756e+05, 862.65548, 70, 70., 4313277.4),\ (1723161717500000000, 61557.05, 3., -1.844370e+05, 874.96776, 71, 71., 4374838.8),\ (1723161718500000000, 61561.95, 3., -1.844370e+05, 874.96776, 71, 71., 4374838.8),\ (1723161719500000000, 61568.05, 2., -1.228746e+05, 887.28024, 72, 72., 4436401.2),\ (1723161720500000000, 61576.55, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161721500000000, 61576.55, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161722500000000, 61589.95, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161723500000000, 61593.95, 0., 2.844000e+02, 911.91204, 74, 74., 4559560.2),\ (1723161724500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161725500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161726500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161727500000000, 61617.05, -2., 1.234944e+05, 936.55404, 76, 76., 4682770.2),\ (1723161728500000000, 61618.15, -3., 1.851120e+05, 948.87756, 77, 77., 4744387.8),\ (1723161729500000000, 61612.55, -3., 1.851120e+05, 948.87756, 77, 77., 4744387.8),\ (1723161730500000000, 61609.95, -2., 1.235000e+05, 961.19996, 78, 78., 4805999.8),\ (1723161731500000000, 61607.95, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161732500000000, 61608.95, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161733500000000, 61606.05, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161734500000000, 61608.45, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161735500000000, 61615.95, -2., 1.234995e+05, 985.84362, 80, 80., 4929218.1),\ (1723161736500000000, 61618.15, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161737500000000, 61605.55, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161738500000000, 61613.85, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161739500000000, 61619.95, -4., 2.467303e+05, 1010.48978, 82, 82., 5052448.9),\ (1723161740500000000, 61636.65, -4., 2.467303e+05, 1010.48978, 82, 82., 5052448.9),\ (1723161741500000000, 61649.75, -5., 3.083675e+05, 1022.81722, 83, 83., 5114086.1),\ (1723161742500000000, 61653.45, -6., 3.700177e+05, 1035.14726, 84, 84., 5175736.3),\ (1723161743500000000, 61668.55, -7., 4.316716e+05, 1047.47804, 85, 85., 5237390.2),\ (1723161744500000000, 61668.55, -7., 4.316716e+05, 1047.47804, 85, 85., 5237390.2),\ (1723161745500000000, 61673.45, -6., 3.700036e+05, 1059.81164, 86, 86., 5299058.2),\ (1723161746500000000, 61675.55, -7., 4.316775e+05, 1072.14642, 87, 87., 5360732.1),\ (1723161747500000000, 61671.35, -8., 4.933535e+05, 1084.48162, 88, 88., 5422408.1),\ (1723161748500000000, 61656.75, -7., 4.316827e+05, 1096.81578, 89, 89., 5484078.9),\ (1723161749500000000, 61660.05, -7., 4.316827e+05, 1096.81578, 89, 89., 5484078.9),\ (1723161750500000000, 61662.05, -8., 4.933433e+05, 1109.1479 , 90, 90., 5545739.5),\ (1723161751500000000, 61652.05, -7., 4.316817e+05, 1121.48022, 91, 91., 5607401.1),\ (1723161752500000000, 61673.45, -7., 4.316817e+05, 1121.48022, 91, 91., 5607401.1),\ (1723161753500000000, 61680.65, -8., 4.933556e+05, 1133.815 , 92, 92., 5669075. ),\ (1723161754500000000, 61672.45, -7., 4.316754e+05, 1146.15104, 93, 93., 5730755.2),\ (1723161755500000000, 61659.95, -6., 3.700035e+05, 1158.48542, 94, 94., 5792427.1),\ (1723161756500000000, 61661.25, -7., 4.316639e+05, 1170.8175 , 95, 95., 5854087.5),\ (1723161757500000000, 61654.25, -7., 4.316639e+05, 1170.8175 , 95, 95., 5854087.5),\ (1723161758500000000, 61650.05, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161759500000000, 61650.05, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161760500000000, 61654.25, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161761500000000, 61652.65, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161762500000000, 61663.95, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161763500000000, 61656.05, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161764500000000, 61656.05, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161765500000000, 61626.35, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161766500000000, 61629.85, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161767500000000, 61629.85, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161768500000000, 61633.05, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161769500000000, 61645.45, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161770500000000, 61645.45, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161771500000000, 61649.75, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161772500000000, 61640.85, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161773500000000, 61642.05, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161774500000000, 61629.85, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161775500000000, 61629.75, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161776500000000, 61635.25, -8., 4.932767e+05, 1232.4631 , 100, 100., 6162315.5),\ (1723161777500000000, 61618.75, -7., 4.316419e+05, 1244.79006, 101, 101., 6223950.3),\ (1723161778500000000, 61615.75, -6., 3.700237e+05, 1257.1137 , 102, 102., 6285568.5),\ (1723161779500000000, 61602.7 , -5., 3.084085e+05, 1269.43674, 103, 103., 6347183.7),\ (1723161780500000000, 61609.75, -4., 2.468063e+05, 1281.75718, 104, 104., 6408785.9),\ (1723161781500000000, 61629.65, -5., 3.084165e+05, 1294.07922, 105, 105., 6470396.1),\ (1723161782500000000, 61634.15, -6., 3.700467e+05, 1306.40526, 106, 106., 6532026.3),\ (1723161783500000000, 61631.95, -5., 3.084131e+05, 1318.73198, 107, 107., 6593659.9),\ (1723161784500000000, 61625.55, -4., 2.467817e+05, 1331.05826, 108, 108., 6655291.3),\ (1723161785500000000, 61617.05, -3., 1.851567e+05, 1343.38326, 109, 109., 6716916.3),\ (1723161786500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161787500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161788500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161789500000000, 61617.05, -1., 6.192050e+04, 1368.0305 , 111, 111., 6840152.5),\ (1723161790500000000, 61616.15, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161791500000000, 61616.15, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161792500000000, 61618.05, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161793500000000, 61642.05, -1., 6.192250e+04, 1392.67754, 113, 113., 6963387.7),\ (1723161794500000000, 61649.95, -2., 1.235651e+05, 1405.00606, 114, 114., 7025030.3),\ (1723161795500000000, 61666.45, -3., 1.852155e+05, 1417.33614, 115, 115., 7086680.7),\ (1723161796500000000, 61658.05, -3., 1.852155e+05, 1417.33614, 115, 115., 7086680.7),\ (1723161797500000000, 61648.45, -2., 1.235579e+05, 1429.66766, 116, 116., 7148338.3),\ (1723161798500000000, 61642.05, -2., 1.235589e+05, 1454.32702, 118, 118., 7271635.1),\ (1723161799500000000, 61638.85, -1., 6.191730e+04, 1466.65534, 119, 119., 7333276.7),\ (1723161800500000000, 61631.95, 0., 2.789000e+02, 1478.98302, 120, 120., 7394915.1),\ (1723161801500000000, 61623.05, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161802500000000, 61626.65, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161803500000000, 61625.05, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161804500000000, 61612.05, 2., -1.229771e+05, 1503.63422, 122, 122., 7518171.1),\ (1723161805500000000, 61618.05, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161806500000000, 61616.15, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161807500000000, 61616.15, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161808500000000, 61602.45, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161809500000000, 61592.05, 2., -1.229664e+05, 1528.27712, 124, 124., 7641385.6),\ (1723161810500000000, 61580.25, 3., -1.845580e+05, 1540.59544, 125, 125., 7702977.2),\ (1723161811500000000, 61580.25, 3., -1.845580e+05, 1540.59544, 125, 125., 7702977.2),\ (1723161812500000000, 61586.25, 2., -1.229772e+05, 1552.9116 , 126, 126., 7764558. ),\ (1723161813500000000, 61594.45, 1., -6.139040e+04, 1565.22896, 127, 127., 7826144.8),\ (1723161814500000000, 61606.85, 0., 2.045000e+02, 1577.54794, 128, 128., 7887739.7),\ (1723161815500000000, 61630.35, -1., 6.181190e+04, 1589.86942, 129, 129., 7949347.1),\ (1723161816500000000, 61638.05, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161817500000000, 61626.25, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161818500000000, 61626.25, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161819500000000, 61613.65, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161820500000000, 61608.15, -1., 6.182950e+04, 1614.51822, 131, 131., 8072591.1),\ (1723161821500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161822500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161823500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161824500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161825500000000, 61622.55, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161826500000000, 61622.55, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161827500000000, 61621.65, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161828500000000, 61615.95, 0., 1.927000e+02, 1651.48902, 134, 134., 8257445.1),\ (1723161829500000000, 61621.55, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161830500000000, 61621.55, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161831500000000, 61614.05, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161832500000000, 61611.55, 2., -1.230363e+05, 1676.13482, 136, 136., 8380674.1),\ (1723161833500000000, 61620.05, 2., -1.230363e+05, 1676.13482, 136, 136., 8380674.1),\ (1723161834500000000, 61622.55, 1., -6.141570e+04, 1688.45894, 137, 137., 8442294.7),\ (1723161835500000000, 61621.55, 1., -6.141470e+04, 1713.10794, 139, 139., 8565539.7),\ (1723161836500000000, 61630.35, 0., 2.073000e+02, 1725.43234, 140, 140., 8627161.7),\ (1723161837500000000, 61613.75, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161838500000000, 61613.75, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161839500000000, 61605.05, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161840500000000, 61616.05, -2., 1.234437e+05, 1750.07962, 142, 142., 8750398.1),\ (1723161841500000000, 61621.55, -3., 1.850603e+05, 1762.40294, 143, 143., 8812014.7),\ (1723161842500000000, 61633.95, -4., 2.466823e+05, 1774.72734, 144, 144., 8873636.7),\ (1723161843500000000, 61638.05, -5., 3.083167e+05, 1787.05422, 145, 145., 8935271.1),\ (1723161844500000000, 61634.95, -4., 2.466791e+05, 1799.38174, 146, 146., 8996908.7),\ (1723161845500000000, 61634.95, -4., 2.466791e+05, 1799.38174, 146, 146., 8996908.7),\ (1723161846500000000, 61638.05, -5., 3.083145e+05, 1811.70882, 147, 147., 9058544.1),\ (1723161847500000000, 61634.95, -5., 3.083155e+05, 1836.36406, 149, 149., 9181820.3),\ (1723161848500000000, 61626.05, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161849500000000, 61629.95, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161850500000000, 61629.95, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161851500000000, 61632.25, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161852500000000, 61635.95, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161853500000000, 61635.95, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161854500000000, 61638.05, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161855500000000, 61636.25, -4., 2.466763e+05, 1873.34502, 152, 152., 9366725.1),\ (1723161856500000000, 61638.05, -4., 2.466763e+05, 1873.34502, 152, 152., 9366725.1),\ (1723161857500000000, 61636.05, -5., 3.083149e+05, 1885.67274, 153, 153., 9428363.7),\ (1723161858500000000, 61641.45, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161859500000000, 61641.45, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161860500000000, 61643.25, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161861500000000, 61657.25, -7., 4.315953e+05, 1910.32882, 155, 155., 9551644.1),\ (1723161862500000000, 61671.45, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161863500000000, 61668.05, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161864500000000, 61669.15, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161865500000000, 61666.75, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161866500000000, 61665.05, -7., 4.315869e+05, 1934.99362, 157, 157., 9674968.1),\ (1723161867500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161868500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161869500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161870500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161871500000000, 61666.75, -7., 4.315799e+05, 1959.65806, 159, 159., 9798290.3),\ (1723161872500000000, 61651.55, -6., 3.699137e+05, 1971.9913 , 160, 160., 9859956.5),\ (1723161873500000000, 61638.05, -5., 3.082627e+05, 1984.3215 , 161, 161., 9921607.5),\ (1723161874500000000, 61634.35, -4., 2.466251e+05, 1996.64902, 162, 162., 9983245.1),\ (1723161875500000000, 61638.85, -4., 2.466251e+05, 1996.64902, 162, 162., 9983245.1),\ (1723161876500000000, 61638.15, -5., 3.082645e+05, 2008.9769 , 163, 163., 10044884.5),\ (1723161877500000000, 61621.65, -4., 2.466269e+05, 2021.30442, 164, 164., 10106522.1),\ (1723161878500000000, 61611.65, -3., 1.850057e+05, 2033.62866, 165, 165., 10168143.3),\ (1723161879500000000, 61614.95, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161880500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161881500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161882500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161883500000000, 61616.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161884500000000, 61623.95, -5., 3.082345e+05, 2058.27442, 167, 167., 10291372.1),\ (1723161885500000000, 61627.95, -6., 3.698589e+05, 2070.5993 , 168, 168., 10352996.5),\ (1723161886500000000, 61621.45, -6., 3.698589e+05, 2070.5993 , 168, 168., 10352996.5),\ (1723161887500000000, 61620.45, -5., 3.082380e+05, 2082.92348, 169, 169., 10414617.4),\ (1723161888500000000, 61617.55, -4., 2.466181e+05, 2095.24746, 170, 170., 10476237.3),\ (1723161889500000000, 61609.45, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161890500000000, 61609.45, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161891500000000, 61605.95, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161892500000000, 61605.95, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161893500000000, 61596.55, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161894500000000, 61595.65, -2., 1.234051e+05, 2119.89006, 172, 172., 10599450.3),\ (1723161895500000000, 61580.75, -1., 6.180990e+04, 2132.2091 , 173, 173., 10661045.5),\ (1723161896500000000, 61575.05, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161897500000000, 61585.05, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161898500000000, 61578.25, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161899500000000, 61578.25, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161900500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161901500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161902500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161903500000000, 61585.05, -2., 1.233929e+05, 2169.15778, 176, 176., 10845788.9)\], dtype={'names': \['timestamp', 'price', 'position', 'balance', 'fee', 'num\_trades', 'trading\_volume', 'trading\_value'\], 'formats': \[') ![]() \[33\]: --- # Examples — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Examples * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/tutorials/examples.rst.txt) * * * Examples[](https://hftbacktest.readthedocs.io/en/latest/tutorials/examples.html#examples "Link to this heading") ================================================================================================================== You can find more examples [here](https://github.com/nkaz001/hftbacktest/tree/master/examples) --- # Migration to v2 — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Migration to v2 * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/migration2.rst.txt) * * * Migration to v2[](https://hftbacktest.readthedocs.io/en/latest/migration2.html#migration-to-v2 "Link to this heading") ======================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/migration2.html#overview "Link to this heading") ---------------------------------------------------------------------------------------------------------- The migration from version 1 to version 2 introduces several significant changes that can cause errors if the same code is used without modification. It is highly recommended to review the updated tutorials. This guide aims to help you avoid common pitfalls during the migration process. Checking Success: Use `elapse() == 0`[](https://hftbacktest.readthedocs.io/en/latest/migration2.html#checking-success-use-elapse-0 "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ In version 1, `elapse` function returns `True` on success and `False` otherwise. Typically, the strategy loop checks for successful elapsing using `while elapse(duration)`. However, in version 2, elapse returns a code instead of a boolean, with `0` indicating success and any other value indicating an error. Consequently, the code should be updated to check if the return value equals `0`. For instance: `while elapse(duration) == 0` If the code remains unchanged, it will fail because a return value of `0` (indicating success) will be treated as `False`. Other methods that involve elapsing, such as `submit_buy_order` or `submit_sell_order`, also return a code similar to `elapse` instead of a boolean. Ensure to check if their return values equal `0` to confirm success instead of checking for `True`. Data Format Changes[](https://hftbacktest.readthedocs.io/en/latest/migration2.html#data-format-changes "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------- The data format fed into HftBacktest has undergone significant changes. It is strongly recommended to reprocess the data from raw data to preserve all information. However, if raw data is unavailable, [`the data conversion utility`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2 "hftbacktest.data.utils.migration2") from v1 to v2 is provided. The major changes are as follows: * SOA to AOS: The format has shifted from a columnar array (SOA) to a structured array (AOS). * Side Column Removal: `side` column has been removed. In version 2, the side is indicated by the `ev` field flags, [`BUY_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.BUY_EVENT "hftbacktest.types.BUY_EVENT") and [`SELL_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.SELL_EVENT "hftbacktest.types.SELL_EVENT") . * Timestamp Handling: In version 1, the data utility corrects the event order by replacing one of the timestamps with `-1` to indicate an invalid event on either the exchange or the local side. In version 2, the validity of events on the exchange or local side is determined by ev field’s [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. * Timestamp Unit: Although not strictly enforced, the timestamp unit has changed from microseconds to nanoseconds. Additionally, the format for live order latency data has changed from SOA to AOS. --- # Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Data * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/data.rst.txt) * * * Data[](https://hftbacktest.readthedocs.io/en/latest/data.html#data "Link to this heading") ============================================================================================ Please see [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) or [Data Preparation](https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html) regarding collecting and converting the feed data. Format[](https://hftbacktest.readthedocs.io/en/latest/data.html#format "Link to this heading") ------------------------------------------------------------------------------------------------ hftbacktest can digest a numpy structured array. The data has 8 fields in the following order. You can also find details in [Event](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/struct.Event.html) . * ev (u64): You can find the possible flag combinations in [Constants](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/index.html#constants) . * exch\_ts (i64): Exchange timestamp, which is the time at which the event occurs on the exchange. * local\_ts (i64): Local timestamp, which is the time at which the event is received by the local. * px (f64): Price * qty (f64): Quantity * order\_id (u64): Order ID is only for the L3 Market-By-Order feed. * ival (i64): Reserved for an additional i64 value * faval (f64): Reserved for an additional f64 value **Raw data** > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22171.70 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22187.30 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.30 | 0.270 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.70 | 0.423 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22195.20 | 2.075 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22209.60 | 4.506 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205116000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | Validation[](https://hftbacktest.readthedocs.io/en/latest/data.html#validation "Link to this heading") -------------------------------------------------------------------------------------------------------- 1. All timestamps must be in the correct order, chronological order. There can be cases where an event happens before another at the exchange, resulting in an earlier exchange timestamp, but it is received locally after the other event. This reverses the chronological order of exchange and local timestamps. To handle this situation, hftbacktest uses the [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. Events flagged with [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") should be in chronological order according to the exchange timestamp, while events flagged with [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") should be in chronological order according to the local timestamp. 2. The exchange timestamp must be earlier than the local timestamp; the feed latency must be positive. Due to potential errors in time synchronization between two sites, the local timestamp may be earlier than the exchange timestamp, resulting in negative latency. The best way to address this is to improve time synchronization using PTP (Precision Time Protocol), which minimizes the possibility of negative latency. However, by adding a base latency or offsetting the size of the negative latency, you can ensure that the data remains valid with only positive latencies, where the local timestamp is always later than the exchange timestamp of the event. See the following example. The exchange timestamp of the depth feed is advanced to the prior trade feed even though the depth feed is received after the trade feed. > 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} This should be converted into the following form. HftBacktest provides [`correct_event_order`](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.correct_event_order "hftbacktest.data.correct_event_order") method to automatically correct this issue. [`validate_event_order`](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.validate_event_order "hftbacktest.data.validate_event_order") helps to check if this issue exists. > EXCH\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > LOCAL\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212385000 | 22177.90 | 0.300 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212480000 | 22177.90 | 0.119 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212621000 | 22177.90 | 0.005 | 0 | 0 | 0.0 | --- # Order Fill — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Order Fill * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/order_fill.rst.txt) * * * Order Fill[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#order-fill "Link to this heading") ============================================================================================================== Exchange Models[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#exchange-models "Link to this heading") ------------------------------------------------------------------------------------------------------------------------ HftBacktest is a market-data replay-based backtesting tool, which means your order cannot make any changes to the simulated market, no market impact is considered. Therefore, one of the most important assumptions is that your order is small enough not to make any market impact. In the end, you must test it in a live market with real market participants and adjust your backtesting based on the discrepancies between the backtesting results and the live outcomes. Hftbacktest offers two types of exchange simulation. [NoPartialFillExchange](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#order-fill-no-partial-fill-exchange) is the default exchange simulation where no partial fills occur. [PartialFillExchange](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#order-fill-partial-fill-exchange) is the extended exchange simulation that accounts for partial fills in specific cases. Since the market-data replay-based backtesting cannot alter the market, some partial fill cases may still be unrealistic, such as taking market liquidity. This is because even if your order takes market liquidity, the replayed market data’s market depth and trades cannot change. It is essential to understand the underlying assumptions in each backtesting simulation. ### NoPartialFillExchange[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#nopartialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#conditions-for-full-execution "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price * Your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price * Your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#liquidity-taking-order "Link to this heading") > Regardless of the quantity at the best, liquidity-taking orders will be fully executed at the best. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [NoPartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.NoPartialFillExchange.html) and [`no_partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange "hftbacktest.BacktestAsset.no_partial_fill_exchange") ### PartialFillExchange[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#partialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#id2 "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price #### Conditions for Partial Execution[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#conditions-for-partial-execution "Link to this heading") Buy order in the order book * Filled by (remaining) sell trade quantity: your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Filled by (remaining) buy trade quantity: your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#id3 "Link to this heading") > Liquidity-taking orders will be executed based on the quantity of the order book, even though the best price and quantity do not change due to your execution. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [PartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.PartialFillExchange.html) and [`partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange "hftbacktest.BacktestAsset.partial_fill_exchange") Queue Models[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#queue-models "Link to this heading") ------------------------------------------------------------------------------------------------------------------ Knowing your order’s queue position is important to achieve accurate order fill simulation in backtesting depending on the liquidity of an order book and trading activities. If an exchange doesn’t provide Market-By-Order, you have to guess it by modeling. HftBacktest currently only supports Market-By-Price that is most crypto exchanges provide and it provides the following queue position models for order fill simulation. Please refer to the details at Models . ![_images/liquidity-and-trade-activities.png](https://hftbacktest.readthedocs.io/en/latest/_images/liquidity-and-trade-activities.png) ### RiskAverseQueueModel[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#riskaversequeuemodel "Link to this heading") This model is the most conservative model in terms of the chance of fill in the queue. The decrease in quantity by cancellation or modification in the order book happens only at the tail of the queue so your order queue position doesn’t change. The order queue position will be advanced only if a trade happens at the price. You can find details below. * [RiskAdverseQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.RiskAdverseQueueModel.html) and [`risk_adverse_queue_model`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model "hftbacktest.BacktestAsset.risk_adverse_queue_model") ### ProbQueueModel[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#probqueuemodel "Link to this heading") Based on a probability model according to your current queue position, the decrease in quantity happens at both before and after the queue position. So your queue position is also advanced according to the probability. This model is implemented as described in * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) * [https://rigtorp.se/2013/06/08/estimating-order-queue-position.html](https://rigtorp.se/2013/06/08/estimating-order-queue-position.html) You can find details below. * [ProbQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ProbQueueModel.html) * [PowerProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc.html) and [`power_prob_queue_model`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model "hftbacktest.BacktestAsset.power_prob_queue_model") * [PowerProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc2.html) and [`power_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2 "hftbacktest.BacktestAsset.power_prob_queue_model2") * [PowerProbQueueFunc3](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc3.html) and [`power_prob_queue_model3`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3 "hftbacktest.BacktestAsset.power_prob_queue_model3") * [LogProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc.html) and [`log_prob_queue_model`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model "hftbacktest.BacktestAsset.log_prob_queue_model") * [LogProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc2.html) and [`log_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2 "hftbacktest.BacktestAsset.log_prob_queue_model2") By default, three variations are provided. These three models have different probability profiles. ![_images/probqueuemodel.png](https://hftbacktest.readthedocs.io/en/latest/_images/probqueuemodel.png) The function f = log(1 + x) exhibits a different probability profile depending on the total quantity at the price level, unlike power functions. ![_images/probqueuemodel_log.png](https://hftbacktest.readthedocs.io/en/latest/_images/probqueuemodel_log.png) ![_images/probqueuemodel2.png](https://hftbacktest.readthedocs.io/en/latest/_images/probqueuemodel2.png) ![_images/probqueuemodel3.png](https://hftbacktest.readthedocs.io/en/latest/_images/probqueuemodel3.png) When you set the function f, it should be as follows. * The probability at 0 should be 0 because if the order is at the head of the queue, all decreases should happen after the order. * The probability at 1 should be 1 because if the order is at the tail of the queue, all decreases should happen before the order. You can see the comparison of the models [here](https://hftbacktest.readthedocs.io/en/latest/tutorials/Probability%20Queue%20Models.html) . ### Implement a custom queue model[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#implement-a-custom-queue-model "Link to this heading") You need to implement the following traits in Rust based on your usage requirements. * [QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.QueueModel.html) * [L3QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.L3QueueModel.html) Please refer to [the queue model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/queue.rs) . References[](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#references "Link to this heading") -------------------------------------------------------------------------------------------------------------- This is initially implemented as described in the following articles. * [http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf](http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf) * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * JIT Compilation Overhead * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/jit_compilation_overhead.rst.txt) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/latest/jit_compilation_overhead.html#jit-compilation-overhead "Link to this heading") ======================================================================================================================================================== HftBacktest takes advantage of Numba’s capabilities, relying on Numba JIT’ed classes. As a result, importing HftBacktest requires JIT compilation, which may take a few seconds. Additionally, the strategy function needs to be JIT’ed’ for performant backtesting, which also takes time to compile. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature. See the example below. from numba import njit \# May take a few seconds from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest \# Enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. asset \= ( BacktestAsset() .linear\_asset(1.0) .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .no\_partial\_fill\_exchange() .intp\_order\_latency(\[\ 'data/latency\_20221003.npz',\ 'data/latency\_20221004.npz',\ 'data/latency\_20221005.npz',\ 'data/latency\_20221006.npz',\ 'data/latency\_20221007.npz'\ \]) .power\_prob\_queue\_model3(3.0) .tick\_size(0.01) .lot\_size(0.001) .trading\_value\_fee\_model(0.0002, 0.0007) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) algo(arguments, hbt) --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Debugging Backtesting and Live Discrepancies * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/debugging_backtesting_and_live_discrepancies.rst.txt) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/latest/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Link to this heading") ==================================================================================================================================================================================================================== Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as `PowerProbQueueModel` series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#order-fill-prob-queue-model) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # Latency Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Latency Models * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/latency_models.rst.txt) * * * Latency Models[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#latency-models "Link to this heading") ========================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#overview "Link to this heading") -------------------------------------------------------------------------------------------------------------- Latency is an important factor that you need to take into account when you backtest your HFT strategy. HftBacktest has three types of latencies. ![_images/latencies.png](https://hftbacktest.readthedocs.io/en/latest/_images/latencies.png) * Feed latency This is the latency between the time the exchange sends the feed events such as order book change or trade and the time it is received by the local. This latency is dealt with through two different timestamps: local timestamp and exchange timestamp. * Order entry latency This is the latency between the time you send an order request and the time it is processed by the exchange’s matching engine. * Order response latency This is the latency between the time the exchange’s matching engine processes an order request and the time the order response is received by the local. The response to your order fill is also affected by this type of latency. ![_images/latency-comparison.png](https://hftbacktest.readthedocs.io/en/latest/_images/latency-comparison.png) Order Latency Models[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#order-latency-models "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------- HftBacktest provides the following order latency models and you can also implement your own latency model. ### ConstantLatency[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#constantlatency "Link to this heading") It’s the most basic model that uses constant latencies. You just set the latencies. You can find details below. * [ConstantLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ConstantLatency.html) and [`constant_latency`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency "hftbacktest.BacktestAsset.constant_latency") ### IntpOrderLatency[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#intporderlatency "Link to this heading") This model interpolates order latency based on the actual order latency data. This is the most accurate among the provided models if you have the data with a fine time interval. You can collect the latency data by submitting unexecutable orders regularly. You can find details below. * [IntpOrderLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html) and [`intp_order_latency`](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency "hftbacktest.BacktestAsset.intp_order_latency") **Data example** req\_ts (request timestamp at local), exch\_ts (exchange timestamp), resp\_ts (receipt timestamp at local), \_padding 1670026844751525000, 1670026844759000000, 1670026844762122000, 0 1670026845754020000, 1670026845762000000, 1670026845770003000, 0 ### FeedLatency[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#feedlatency "Link to this heading") If the live order latency data is unavailable, you can generate artificial order latency using feed latency. Please refer to [this tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) for guidance. ### Implement your own order latency model[](https://hftbacktest.readthedocs.io/en/latest/latency_models.html#implement-your-own-order-latency-model "Link to this heading") You need to implement the following trait. * [LatencyModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.LatencyModel.html) Please refer to [the latency model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/latency.rs) . --- # Market Maker Program — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Market Maker Program * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/market_maker_program.rst.txt) * * * Market Maker Program[](https://hftbacktest.readthedocs.io/en/latest/market_maker_program.html#market-maker-program "Link to this heading") ============================================================================================================================================ **DISCLAIMER:** This document provides a list of market maker program. However, the information may not be fully up-to-date or accurate. Always verify details directly with the respective exchange. Use this list as a general reference only. If you find outdated or incorrect information, please submit a GitHub issue to notify us. Binance Futures[](https://hftbacktest.readthedocs.io/en/latest/market_maker_program.html#binance-futures "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------- * Server Location: AWS Tokyo * Highest Rebates: 0.005% ~ 0.008% * Benefits: Higher rate limits, low-latency connection, tailored configuration, onboarding trial period Links: * [Binance Updates USDⓢ-Margined Futures Liquidity Provider Program (2025-03-03)](https://www.binance.com/en/support/announcement/detail/1b1ce5a98e91435aac13d078fe1a94ed) * [Fees & Transactions Overview](https://www.binance.com/en/fee/umMaker) * [Portfolio Margin](https://www.binance.com/en/portfolio-margin) * [Binance API](https://www.binance.com/en/binance-api) Bybit Futures[](https://hftbacktest.readthedocs.io/en/latest/market_maker_program.html#bybit-futures "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------ * Server Location: AWS Singapore * Highest Rebates: 0.0025% ~ 0.0125% * Benefits: Higher rate limits, low-latency connection, tailored configuration, onboarding trial period Links: * [Bybit Market Maker Program](https://www.bybit.com/en/help-center/article/Introduction-to-the-Market-Maker-Incentive-Program) * [Bybit Server Locations](https://bybit-exchange.github.io/docs/faq#where-are-bybits-servers-located) * [Bybit API](https://www.bybit.com/future-activity/en/developer) OKX Futures[](https://hftbacktest.readthedocs.io/en/latest/market_maker_program.html#okx-futures "Link to this heading") -------------------------------------------------------------------------------------------------------------------------- * Server Location: Alibaba Cloud Hong Kong * Highest Rebates: 0.005% * Benefits: Higher rate limits, low-latency connection, tailored configuration, onboarding trial period Links: * [OKX Fee Schedule](https://www.okx.com/fees) * [Market Maker Program](https://www.okx.com/docs-v5/en/#overview-market-maker-program) * [OKX API](https://www.okx.com/docs-v5/en/) Hyperliquid[](https://hftbacktest.readthedocs.io/en/latest/market_maker_program.html#hyperliquid "Link to this heading") -------------------------------------------------------------------------------------------------------------------------- * Node Location: AWS Tokyo * Highest Rebates: 0.003% Links: * [Hyperliquid Fee Schedule](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees) * [Hyperliquid DEX Node](https://github.com/hyperliquid-dex/node) * [API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Index * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#M) | [**N**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#N) | [**O**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#P) | [**Q**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#Q) | [**R**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#T) | [**U**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#U) | [**V**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/latest/genindex.html#W) A - | | | | --- | --- | | * [ALL\_ASSETS (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.ALL_ASSETS)

* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset)

* [balance (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.balance)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_qty (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_qty)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_qty)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_qty (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_qty)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_qty)

* [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (FuseMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.close)
* [(HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)

* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency)

* [constant\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.constant_order_latency) | * [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size)

* [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.databento)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_fuse() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_fuse)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [fee (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.fee)

* [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency)

* [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FILLED) | * [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FOK)

* [fused\_events (FuseMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.fused_events)

* [FuseMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth)
* [(class in hftbacktest.data)](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.FuseMarketDepth) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.databento
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [l3\_fifo\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.l3_fifo_queue_model)

* [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [latency\_offset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.latency_offset)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.LIMIT)

* [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset) | * [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord)

* [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Metric)

* [modify() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.modify)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.modify)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.databento](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [num\_trades (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.num_trades)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [parallel\_load() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.parallel_load)

* [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.position)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.price_tick)

* [process\_event() (FuseMarketDepth method)](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.process_event) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.ReturnOverMDD)

* [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.ReturnOverTrade) | * [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_lb\_tick (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.roi_lb_tick)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [roi\_ub\_tick (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.roi_ub_tick)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [StateValues (class in hftbacktest.state)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues) | * [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Stats)

* [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.trading_value)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [trading\_volume (StateValues property)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.trading_volume)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/latest/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/latest/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # Constants — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Constants * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/reference/constants.rst.txt) * * * Constants[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#constants "Link to this heading") ===================================================================================================================== EXCH\_EVENT _\= 2147483648_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.EXCH_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the exchange processor at the exchange timestamp. LOCAL\_EVENT _\= 1073741824_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.LOCAL_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the local processor at the local timestamp. BUY\_EVENT _\= 536870912_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.BUY_EVENT "Link to this definition") Indicates a buy, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means a bid-side event, while when combined with a trade event, it means that the trade initiator is a buyer. SELL\_EVENT _\= 268435456_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.SELL_EVENT "Link to this definition") Indicates a sell, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means an ask-side event, while when combined with a trade event, it means that the trade initiator is a seller. MARKET _\= 1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.MARKET "Link to this definition") MARKET LIMIT _\= 0_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.LIMIT "Link to this definition") LIMIT BUY _\= 1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.BUY "Link to this definition") In the market depth event, this indicates the bid side; in the market trade event, it indicates that the trade initiator is a buyer. SELL _\= \-1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.SELL "Link to this definition") In the market depth event, this indicates the ask side; in the market trade event, it indicates that the trade initiator is a seller. NONE _\= 0_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NONE "Link to this definition") NONE NEW _\= 1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NEW "Link to this definition") NEW EXPIRED _\= 2_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.EXPIRED "Link to this definition") EXPIRED FILLED _\= 3_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FILLED "Link to this definition") FILLED PARTIALLY\_FILLED _\= 5_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "Link to this definition") PARTIALLY\_FILLED CANCELED _\= 4_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.CANCELED "Link to this definition") CANCELED REJECTED _\= 6_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.REJECTED "Link to this definition") REJECTED GTC _\= 0_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTC "Link to this definition") Good ‘till cancel GTX _\= 1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTX "Link to this definition") Post only FOK _\= 2_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FOK "Link to this definition") Fill or kill IOC _\= 3_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.IOC "Link to this definition") Immediate or cancel ALL\_ASSETS _\= \-1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.ALL_ASSETS "Link to this definition") Indicates all assets. DEPTH\_EVENT _\= 1_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_EVENT "Link to this definition") Indicates that the market depth is changed. TRADE\_EVENT _\= 2_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.TRADE_EVENT "Link to this definition") Indicates that a trade occurs in the market. DEPTH\_CLEAR\_EVENT _\= 3_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT "Link to this definition") Indicates that the market depth is cleared. DEPTH\_SNAPSHOT\_EVENT _\= 4_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT "Link to this definition") Indicates that the market depth snapshot is received. UNTIL\_END\_OF\_DATA _\= 9223372036854775807_[](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA "Link to this definition") Indicates that one should continue until the end of the data. --- # Initialization — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Initialization * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/reference/initialization.rst.txt) * * * Initialization[](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#initialization "Link to this heading") ==================================================================================================================================== _class_ BacktestAsset[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest.html#BacktestAsset) [](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset "Link to this definition") data(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest.html#BacktestAsset.data) [](https://hftbacktest.readthedocs.io/en/latest/reference/initialization.html#hftbacktest.BacktestAsset.data "Link to this definition") Sets the feed data. Parameters: **data** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") _|_ [_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _\]_ _|_ [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.4)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ _dtype__(__\[__(__'ev'__,_ _' * [`NONE`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NONE "hftbacktest.order.NONE") > for no ongoing request. > > * [`NEW`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NEW "hftbacktest.order.NEW") > for submitting a new order. > > * [`CANCELED`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.CANCELED "hftbacktest.order.CANCELED") > for canceling the order. > _property_ status_: uint8_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.status "Link to this definition") Returns the order status. This can be one of the following values, but may vary depending on the exchange model. * [`NONE`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NONE "hftbacktest.order.NONE") * [`NEW`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.NEW "hftbacktest.order.NEW") * [`EXPIRED`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.EXPIRED "hftbacktest.order.EXPIRED") * [`FILLED`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FILLED "hftbacktest.order.FILLED") * [`CANCELED`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.CANCELED "hftbacktest.order.CANCELED") * [`PARTIALLY_FILLED`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "hftbacktest.order.PARTIALLY_FILLED") _property_ side_: uint8_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.side "Link to this definition") Returns the order side. * [`BUY`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.BUY "hftbacktest.order.BUY") * [`SELL`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.SELL "hftbacktest.order.SELL") _property_ time\_in\_force_: uint8_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.order.Order.time_in_force "Link to this definition") Returns the Time-In-Force of the order. This can be one of the following values, but may vary depending on the exchange model. > * [`GTC`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTC "hftbacktest.order.GTC") > > * [`GTX`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.GTX "hftbacktest.order.GTX") > > * [`FOK`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.FOK "hftbacktest.order.FOK") > > * [`IOC`](https://hftbacktest.readthedocs.io/en/latest/reference/constants.html#hftbacktest.order.IOC "hftbacktest.order.IOC") > _class_ StateValues[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest/state.html#StateValues) [](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues "Link to this definition") Parameters: **arr** (_Array__(__Record__(__\[__(__'position'__,_ _{'type': float64__,_ _'offset': 0__,_ _'alignment': None__,_ _'title': None__,_ _}__)__,_ _(__'balance'__,_ _{'type': float64__,_ _'offset': 8__,_ _'alignment': None__,_ _'title': None__,_ _}__)__,_ _(__'fee'__,_ _{'type': float64__,_ _'offset': 16__,_ _'alignment': None__,_ _'title': None__,_ _}__)__,_ _(__'num\_trades'__,_ _{'type': int64__,_ _'offset': 24__,_ _'alignment': None__,_ _'title': None__,_ _}__)__,_ _(__'trading\_volume'__,_ _{'type': float64__,_ _'offset': 32__,_ _'alignment': None__,_ _'title': None__,_ _}__)__,_ _(__'trading\_value'__,_ _{'type': float64__,_ _'offset': 40__,_ _'alignment': None__,_ _'title': None__,_ _}__)__\]__,_ _48__,_ _True__)__,_ _1__,_ _'A'__,_ _False__,_ _aligned=True__)_) _property_ position_: float64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.position "Link to this definition") Returns the open position. _property_ balance_: float64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.balance "Link to this definition") Returns the cash balance. _property_ fee_: float64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.fee "Link to this definition") Returns the accumulated fee. _property_ num\_trades_: int64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.num_trades "Link to this definition") Returns the total number of trades. _property_ trading\_volume_: float64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.trading_volume "Link to this definition") Returns the total trading volume. _property_ trading\_value_: float64_[](https://hftbacktest.readthedocs.io/en/latest/reference/backtester.html#hftbacktest.state.StateValues.trading_value "Link to this definition") Returns the total trading value. --- # Data Utilities — hftbacktest * [](https://hftbacktest.readthedocs.io/en/latest/index.html) * Data Utilities * [View page source](https://hftbacktest.readthedocs.io/en/latest/_sources/reference/data_utilities.rst.txt) * * * Data Utilities[](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#data-utilities "Link to this heading") ==================================================================================================================================== * [hftbacktest.data.utils.binancefutures module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html) * [`convert()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert) * [hftbacktest.data.utils.binancehistmktdata module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html) * [`convert()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert) * [`convert_snapshot()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot) * [hftbacktest.data.utils.databento module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.databento.html) * [`convert()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert) * [hftbacktest.data.utils.difforderbooksnapshot module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html) * [`DiffOrderBookSnapshot`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) * [`DiffOrderBookSnapshot.class_type`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type) * [hftbacktest.data.utils.migration2 module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html) * [`convert()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert) * [hftbacktest.data.utils.snapshot module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.snapshot.html) * [`create_last_snapshot()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot) * [hftbacktest.data.utils.tardis module](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html) * [`convert()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert) * [`convert_fuse()`](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_fuse) _class_ FuseMarketDepth(_tick\_size_, _lot\_size_)[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest/binding.html#FuseMarketDepth) [](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth "Link to this definition") This combines the real-time Level-1 book ticker stream with the conflated Level-2 depth stream to produce the most frequent and granular depth events possible. Parameters: * **tick\_size** (_float64_) – tick size for the asset being processed. * **lot\_size** (_float64_) – lot size for the asset being processed. close()[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest/binding.html#FuseMarketDepth.close) [](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.close "Link to this definition") Releases resources associated with this FuseMarketDepth instance. This method must be called to free the underlying memory allocated by the native implementation. Return type: None process\_event(_ev_, _index_, _add_)[\[source\]](https://hftbacktest.readthedocs.io/en/latest/_modules/hftbacktest/binding.html#FuseMarketDepth.process_event) [](https://hftbacktest.readthedocs.io/en/latest/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.process_event "Link to this definition") Processes a market event at the given index. Parameters: * **ev** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.4)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ _dtype__(__\[__(__'ev'__,_ _'\] 2.95M 5.66MB/s in 0.5s 2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s 2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp. \[11\]: from hftbacktest.data.utils import tardis data \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\] ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order \[12\]: pl.DataFrame(data) \[12\]: shape: (27\_532\_602, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 | | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 | | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 | | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: \_ \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000 ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don’t need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev’s SOD snapshot in the converted file using the option. --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ \* \`Level-3 Backtesting \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Market Making with Alpha - Basis \`\_ \* \`Market Making with Alpha - APT \`\_ \* \`Queue-Based Market Making in Large Tick Size Assets \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.10+-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.87-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/High-Frequency Grid Trading - Comparison Across Other Exchanges tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/Level-3 Backtesting tutorials/Market Making with Alpha - Order Book Imbalance tutorials/Market Making with Alpha - Basis tutorials/Market Making with Alpha - APT tutorials/Queue-Based Market Making in Large Tick Size Assets tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.10-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.80.1-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high frequency trading and market making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on Level-2 Market-By-Price and Level-3 Market-By-Order feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot for quick prototyping and testing using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Tutorials you’ll likely find interesting: \* \`High-Frequency Grid Trading - Simplified from GLFT \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Market Making with Alpha - APT \`\_ \* \`Accelerated Backtesting \`\_ Why Accurate Backtesting Matters — Not Just Conservative Approach ================================================================= Trading is a highly competitive field where only the small edges usually exist, but they can still make a significant difference. Because of this, backtesting must accurately simulate real-world conditions.: It should neither rely on an overly conservative approach that hides these small edges and profit opportunities, nor on an overly aggressive one that overstates them through unrealistic simulation. Or at the very least, you should clearly understand what differs from live trading and by how much, since sometimes fully accurate backtesting is not practical due to the time it requires. This is not about overfitting at the start—before you even consider issues like overfitting, you need confidence that your backtesting truly reflects real-world execution. For example, if you run a live trading strategy in January 2025, the backtest for that exact period should produce results that closely align with the actual results. Once you’ve validated that your backtesting can accurately reproduce live trading results, then you can proceed to deeper research, optimization, and considerations around overfitting. Accurate backtesting is the foundation. Without it, all further analysis—whether conservative or aggressive—becomes unreliable. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`High-Frequency Grid Trading - Comparison Across Other Exchanges \`\_ \* \`High-Frequency Grid Trading - Simplified from GLFT \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ \* \`Level-3 Backtesting \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Market Making with Alpha - Basis \`\_ \* \`Market Making with Alpha - APT \`\_ \* \`Queue-Based Market Making in Large Tick Size Assets \`\_ \* \`Fusing Depth Data \`\_ \* \`Accelerated Backtesting \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.11+-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.89-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/High-Frequency Grid Trading - Comparison Across Other Exchanges tutorials/High-Frequency Grid Trading - Simplified from GLFT tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/Level-3 Backtesting tutorials/Market Making with Alpha - Order Book Imbalance tutorials/Market Making with Alpha - Basis tutorials/Market Making with Alpha - APT tutorials/Queue-Based Market Making in Large Tick Size Assets tutorials/Fusing Depth Data tutorials/Accelerated Backtesting tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies Market Maker Program .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ \* \`Level-3 Backtesting \`\_ \* \`Market Making with Alpha - Order Book Imbalance \`\_ \* \`Queue-Based Market Making in Large Tick Size Assets \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.10-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.84-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/Level-3 Backtesting tutorials/Market Making with Alpha - Order Book Imbalance tutorials/Queue-Based Market Making in Large Tick Size Assets tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Unknown .. meta:: :google-site-verification: IJcyhIoS28HF0lp6fGjBEOC65kVecelW6ZsFhbDaD-A =========== HftBacktest =========== |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| High-Frequency Trading Backtesting Tool ======================================= This framework is designed for developing high-frequency trading and market-making strategies. It focuses on accounting for both feed and order latencies, as well as the order queue position for order fill simulation. The framework aims to provide more accurate market replay-based backtesting, based on full order book and trade tick feed data. Key Features ============ The experimental features are currently in the early stages of development, having been completely rewritten in Rust to support the following features. \* Working in \`Numba \`\_ JIT function (Python). \* Complete tick-by-tick simulation with a customizable time interval or based on the feed and order receipt. \* Full order book reconstruction based on L2 Market-By-Price and L3 Market-By-Order (Rust-only, WIP) feeds. \* Backtest accounting for both feed and order latency, using provided models or your own custom model. \* Order fill simulation that takes into account the order queue position, using provided models or your own custom model. \* Backtesting of multi-asset and multi-exchange models \* Deployment of a live trading bot using the same algorithm code: currently for Binance Futures and Bybit. (Rust-only) Documentation ============= See \`full document here \`\_. Getting started =============== Installation ------------ hftbacktest supports Python 3.10+. You can install hftbacktest using \`\`pip\`\`: .. code-block:: console pip install hftbacktest Or you can clone the latest development version from the Git repository with: .. code-block:: console git clone https://github.com/nkaz001/hftbacktest Data Source & Format -------------------- Please see \`Data \`\_ or \`Data Preparation \`\_. You can also find some data \`here \`\_, hosted by the supporter. A Quick Example --------------- Get a glimpse of what backtesting with hftbacktest looks like with these code snippets: .. code-block:: python @njit def market\_making\_algo(hbt): asset\_no = 0 tick\_size = hbt.depth(asset\_no).tick\_size lot\_size = hbt.depth(asset\_no).lot\_size # in nanoseconds while hbt.elapse(10\_000\_000) == 0: hbt.clear\_inactive\_orders(asset\_no) a = 1 b = 1 c = 1 hs = 1 # Alpha, it can be a combination of several indicators. forecast = 0 # In HFT, it can be various measurements of short-term market movements, # such as the high-low range in the last X minutes. volatility = 0 # Delta risk, it can be a combination of several risks. position = hbt.position(asset\_no) risk = (c + volatility) \* position half\_spread = (c + volatility) \* hs max\_notional\_position = 1000 notional\_qty = 100 depth = hbt.depth(asset\_no) mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0 # fair value pricing = mid\_price + a \* forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a \* forecast # risk skewing = -b \* risk reservation\_price = mid\_price + a \* forecast - b \* risk new\_bid = reservation\_price - half\_spread new\_ask = reservation\_price + half\_spread new\_bid\_tick = min(np.round(new\_bid / tick\_size), depth.best\_bid\_tick) new\_ask\_tick = max(np.round(new\_ask / tick\_size), depth.best\_ask\_tick) order\_qty = np.round(notional\_qty / mid\_price / lot\_size) \* lot\_size # Elapses a process time. if not hbt.elapse(1\_000\_000) != 0: return False last\_order\_id = -1 update\_bid = True update\_ask = True buy\_limit\_exceeded = position \* mid\_price > max\_notional\_position sell\_limit\_exceeded = position \* mid\_price < -max\_notional\_position orders = hbt.orders(asset\_no) order\_values = orders.values() while order\_values.has\_next(): order = order\_values.get() if order.side == BUY: if order.price\_tick == new\_bid\_tick or buy\_limit\_exceeded: update\_bid = False if order.cancellable and (update\_bid or buy\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id elif order.side == SELL: if order.price\_tick == new\_ask\_tick or sell\_limit\_exceeded: update\_ask = False if order.cancellable and (update\_ask or sell\_limit\_exceeded): hbt.cancel(asset\_no, order.order\_id, False) last\_order\_id = order.order\_id # It can be combined with a grid trading strategy by submitting multiple orders to capture better spreads and # have queue position. # This approach requires more sophisticated logic to efficiently manage resting orders in the order book. if update\_bid: # There is only one order at a given price, with new\_bid\_tick used as the order ID. order\_id = new\_bid\_tick hbt.submit\_buy\_order(asset\_no, order\_id, new\_bid\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id if update\_ask: # There is only one order at a given price, with new\_ask\_tick used as the order ID. order\_id = new\_ask\_tick hbt.submit\_sell\_order(asset\_no, order\_id, new\_ask\_tick \* tick\_size, order\_qty, GTX, LIMIT, False) last\_order\_id = order\_id # All order requests are considered to be requested at the same time. # Waits until one of the order responses is received. if last\_order\_id >= 0: # Waits for the order response for a maximum of 5 seconds. timeout = 5\_000\_000\_000 if not hbt.wait\_order\_response(asset\_no, last\_order\_id, timeout): return False return True Tutorials ========= \* \`Data Preparation \`\_ \* \`Getting Started \`\_ \* \`Working with Market Depth and Trades \`\_ \* \`Integrating Custom Data \`\_ \* \`Making Multiple Markets - Introduction \`\_ \* \`High-Frequency Grid Trading \`\_ \* \`Impact of Order Latency \`\_ \* \`Order Latency Data \`\_ \* \`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading \`\_ \* \`Making Multiple Markets \`\_ \* \`Risk Mitigation through Price Protection in Extreme Market Conditions \`\_ Examples ======== You can find more examples in \`examples \`\_ directory and \`Rust examples \`\_. The complete process of backtesting Binance Futures --------------------------------------------------- \`high-frequency gridtrading \`\_: The complete process of backtesting Binance Futures using a high-frequency grid trading strategy implemented in Rust. Migration to V2 =============== Please see the \`migration guide \`\_. Roadmap ======= Currently, new features are being implemented in Rust due to the limitations of Numba, as performance is crucial given the size of the high-frequency data. The imminent task is to integrate hftbacktest in Python with hftbacktest in Rust by using the Rust implementation as the backend. Meanwhile, the data format, which is currently different, needs to be unified. On the pure Python side, the performance reporting tool should be improved to provide more performance metrics with increased speed. Please see the \`roadmap \`\_. Contributing ============ Thank you for considering contributing to hftbacktest! Welcome any and all help to improve the project. If you have an idea for an enhancement or a bug fix, please open an issue or discussion on GitHub to discuss it. The following items are examples of contributions you can make to this project: Please see the \`roadmap \`\_. .. |python| image:: https://shields.io/badge/python-3.10-blue :alt: Python Version :target: https://www.python.org/ .. |codeql| image:: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml/badge.svg?branch=master&event=push :alt: CodeQL :target: https://github.com/nkaz001/hftbacktest/actions/workflows/codeql.yml .. |pypi| image:: https://badge.fury.io/py/hftbacktest.svg :alt: Package Version :target: https://pypi.org/project/hftbacktest .. |downloads| image:: https://static.pepy.tech/badge/hftbacktest :alt: Downloads :target: https://pepy.tech/project/hftbacktest .. |crates| image:: https://img.shields.io/crates/v/hftbacktest.svg :alt: Rust crates.io version :target: https://crates.io/crates/hftbacktest .. |license| image:: https://img.shields.io/badge/License-MIT-green.svg :alt: License :target: https://github.com/nkaz001/hftbacktest/blob/master/LICENSE .. |docs| image:: https://readthedocs.org/projects/hftbacktest/badge/?version=latest :target: https://hftbacktest.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |roadmap| image:: https://img.shields.io/badge/Roadmap-gray :target: https://github.com/nkaz001/hftbacktest/blob/master/ROADMAP.md :alt: Roadmap .. |github| image:: https://img.shields.io/github/stars/nkaz001/hftbacktest?style=social :target: https://github.com/nkaz001/hftbacktest :alt: Github .. |rustc| image:: https://shields.io/badge/rustc-1.79-blue :alt: Rust Version :target: https://www.rust-lang.org/ .. toctree:: :maxdepth: 1 :caption: Tutorials :hidden: tutorials/Data Preparation tutorials/Getting Started tutorials/Working with Market Depth and Trades tutorials/Integrating Custom Data tutorials/Making Multiple Markets - Introduction tutorials/High-Frequency Grid Trading tutorials/Impact of Order Latency tutorials/Order Latency Data tutorials/GLFT Market Making Model and Grid Trading tutorials/Making Multiple Markets tutorials/Probability Queue Models tutorials/Risk Mitigation through Price Protection in Extreme Market Conditions tutorials/examples .. toctree:: :maxdepth: 2 :caption: User Guide :hidden: Migration To v2 Data Latency Models Order Fill JIT Compilation Overhead Debugging Backtesting and Live Discrepancies .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: Initialization Backtester Constants Statistics Data Validation Data Utilities Index --- # Unknown { "cells": \[ { "cell\_type": "markdown", "id": "b0e12d13", "metadata": {}, "source": \[ "# Data Preparation" \] }, { "cell\_type": "markdown", "id": "147f934d", "metadata": {}, "source": \[ "To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself." \] }, { "cell\_type": "markdown", "id": "d73f6b70", "metadata": {}, "source": \[ "## Getting started from Binance Futures' raw feed data\\n", "\\n", "You can collect Binance Futures feed yourself using \[Data Collector\](https://github.com/nkaz001/hftbacktest/tree/master/collector)." \] }, { "cell\_type": "code", "execution\_count": 1, "id": "4694ac12", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "b'1723161255030314667 {\\"stream\\":\\"btcusdt@depth@0ms\\",\\"data\\":{\\"e\\":\\"depthUpdate\\",\\"E\\":1723161256299,\\"T\\":1723161256298,\\"s\\":\\"BTCUSDT\\",\\"U\\":5123107832006,\\"u\\":5123107837557,\\"pu\\":5123107831937,\\"b\\":\[\[\\"58710.20\\",\\"0.014\\"\],\[\\"61496.50\\",\\"0.010\\"\],\[\\"61510.90\\",\\"0.000\\"\],\[\\"61641.50\\",\\"1.211\\"\],\[\\"61652.80\\",\\"0.195\\"\],\[\\"61653.30\\",\\"0.072\\"\],\[\\"61653.70\\",\\"0.067\\"\],\[\\"61657.90\\",\\"0.067\\"\],\[\\"61668.50\\",\\"0.086\\"\],\[\\"61670.60\\",\\"0.161\\"\],\[\\"61672.50\\",\\"0.821\\"\],\[\\"61673.60\\",\\"0.048\\"\],\[\\"61675.60\\",\\"0.050\\"\],\[\\"61684.50\\",\\"0.765\\"\],\[\\"61686.20\\",\\"0.008\\"\],\[\\"61701.80\\",\\"0.331\\"\],\[\\"61703.10\\",\\"0.238\\"\],\[\\"61715.90\\",\\"0.308\\"\],\[\\"61721.60\\",\\"0.235\\"\],\[\\"61724.10\\",\\"0.002\\"\],\[\\"61737.00\\",\\"0.015\\"\],\[\\"61739.00\\",\\"0.000\\"\],\[\\"61740.10\\",\\"0.008\\"\],\[\\"61740.50\\",\\"12.111\\"\],\[\\"61756.90\\",\\"0.550\\"\],\[\\"61758.70\\",\\"0.003\\"\],\[\\"61763.20\\",\\"0.014\\"\],\[\\"61764.10\\",\\"0.168\\"\],\[\\"61764.30\\",\\"0.000\\"\],\[\\"61765.50\\",\\"0.000\\"\],\[\\"61767.40\\",\\"0.004\\"\],\[\\"61768.20\\",\\"0.120\\"\],\[\\"61768.60\\",\\"0.020\\"\],\[\\"61768.90\\",\\"0.099\\"\],\[\\"61770.80\\",\\"0.049\\"\],\[\\"61771.10\\",\\"0.612\\"\],\[\\"61771.70\\",\\"0.010\\"\],\[\\"61773.50\\",\\"0.035\\"\],\[\\"61773.80\\",\\"0.025\\"\],\[\\"61774.00\\",\\"0.112\\"\],\[\\"61775.60\\",\\"0.010\\"\],\[\\"61776.00\\",\\"0.084\\"\],\[\\"61778.30\\",\\"0.000\\"\],\[\\"61778.60\\",\\"0.408\\"\],\[\\"61779.30\\",\\"0.020\\"\],\[\\"61779.60\\",\\"0.220\\"\],\[\\"61783.80\\",\\"0.002\\"\],\[\\"61784.90\\",\\"0.102\\"\],\[\\"61785.00\\",\\"0.000\\"\],\[\\"61788.10\\",\\"0.140\\"\],\[\\"61789.50\\",\\"0.000\\"\],\[\\"61798.70\\",\\"0.153\\"\],\[\\"61800.20\\",\\"2.507\\"\]\],\\"a\\":\[\[\\"61800.30\\",\\"3.330\\"\],\[\\"61804.60\\",\\"0.057\\"\],\[\\"61810.00\\",\\"0.285\\"\],\[\\"61812.00\\",\\"0.732\\"\],\[\\"61814.90\\",\\"0.000\\"\],\[\\"61817.20\\",\\"0.000\\"\],\[\\"61818.70\\",\\"0.040\\"\],\[\\"61824.00\\",\\"0.860\\"\],\[\\"61829.10\\",\\"0.185\\"\],\[\\"61831.30\\",\\"0.008\\"\],\[\\"61831.40\\",\\"0.501\\"\],\[\\"61839.00\\",\\"0.002\\"\],\[\\"61840.00\\",\\"0.192\\"\],\[\\"61856.30\\",\\"0.003\\"\],\[\\"61857.10\\",\\"0.027\\"\],\[\\"61857.40\\",\\"0.000\\"\],\[\\"61858.80\\",\\"0.005\\"\],\[\\"61858.90\\",\\"0.032\\"\],\[\\"61859.60\\",\\"0.034\\"\],\[\\"61874.80\\",\\"0.006\\"\],\[\\"61893.40\\",\\"0.335\\"\],\[\\"61911.90\\",\\"0.014\\"\],\[\\"61925.90\\",\\"0.000\\"\],\[\\"61930.50\\",\\"0.015\\"\],\[\\"61945.10\\",\\"0.000\\"\],\[\\"61953.70\\",\\"0.000\\"\],\[\\"62144.00\\",\\"0.006\\"\],\[\\"63113.70\\",\\"0.000\\"\],\[\\"65880.70\\",\\"15.918\\"\]\]}}\\\\n'\\n", "b'1723161255088169167 {\\"stream\\":\\"btcusdt@bookTicker\\",\\"data\\":{\\"e\\":\\"bookTicker\\",\\"u\\":5123107839020,\\"s\\":\\"BTCUSDT\\",\\"b\\":\\"61800.20\\",\\"B\\":\\"2.507\\",\\"a\\":\\"61800.30\\",\\"A\\":\\"2.510\\",\\"T\\":1723161256313,\\"E\\":1723161256313}}\\\\n'\\n", "b'1723161255088176367 {\\"stream\\":\\"btcusdt@trade\\",\\"data\\":{\\"e\\":\\"trade\\",\\"E\\":1723161256322,\\"T\\":1723161256322,\\"s\\":\\"BTCUSDT\\",\\"t\\":5266583935,\\"p\\":\\"61800.30\\",\\"q\\":\\"0.006\\",\\"X\\":\\"MARKET\\",\\"m\\":false}}\\\\n'\\n", "b'1723161255088181667 {\\"stream\\":\\"btcusdt@bookTicker\\",\\"data\\":{\\"e\\":\\"bookTicker\\",\\"u\\":5123107840008,\\"s\\":\\"BTCUSDT\\",\\"b\\":\\"61800.20\\",\\"B\\":\\"2.507\\",\\"a\\":\\"61800.30\\",\\"A\\":\\"2.504\\",\\"T\\":1723161256322,\\"E\\":1723161256322}}\\\\n'\\n", "b'1723161255088182467 {\\"stream\\":\\"btcusdt@bookTicker\\",\\"data\\":{\\"e\\":\\"bookTicker\\",\\"u\\":5123107840016,\\"s\\":\\"BTCUSDT\\",\\"b\\":\\"61800.20\\",\\"B\\":\\"2.507\\",\\"a\\":\\"61800.30\\",\\"A\\":\\"2.522\\",\\"T\\":1723161256322,\\"E\\":1723161256322}}\\\\n'\\n" \] } \], "source": \[ "import gzip\\n", "\\n", "with gzip.open('usdm/btcusdt\_20240808.gz', 'r') as f:\\n", " for i in range(5):\\n", " line = f.readline()\\n", " print(line)" \] }, { "cell\_type": "markdown", "id": "2f27f86a", "metadata": {}, "source": \[ "The first token of the line is timestamp received by local.\\n", "\\n", "\ \ \\n", " \\n", "\*\*Note:\*\* The timestamp is in nanoseconds.\\n", " \\n", "\ \ " \] }, { "cell\_type": "markdown", "id": "1ce42cbb", "metadata": {}, "source": \[ "The data needs to be converted to normalized data that can be fed into HftBacktest. \\n", "\`convert\` method also attempts to correct timestamps by reordering the rows." \] }, { "cell\_type": "code", "execution\_count": 2, "id": "e72631fd-93a2-4b1c-a753-9534511d6563", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "Correcting the latency\\n", "local\_timestamp is ahead of exch\_timestamp by 1272156851\\n", "Correcting the event order\\n" \] } \], "source": \[ "import numpy as np\\n", "\\n", "from hftbacktest.data.utils import binancefutures\\n", "\\n", "data = binancefutures.convert(\\n", " 'usdm/btcusdt\_20240808.gz',\\n", " combined\_stream=True\\n", ")" \] }, { "cell\_type": "markdown", "id": "4c1a2a62", "metadata": {}, "source": \[ "Normalized data as follows. You can find more details on \[Data\](https://hftbacktest.readthedocs.io/en/latest/data.html)." \] }, { "cell\_type": "code", "execution\_count": 3, "id": "75f3daa2-f158-4c49-9e4b-1bf175ac0c7e", "metadata": {}, "outputs": \[ { "data": { "text/html": \[ "\ \ \\n", "shape: (491\_973, 8)\ \ | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | 3758096385 | 1723161256298000000 | 1723161256302471518 | 58710.2 | 0.014 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61496.5 | 0.01 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61510.9 | 0.0 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61641.5 | 1.211 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61652.8 | 0.195 | 0 | 0 | 0.0 |\ | … | … | … | … | … | … | … | … |\ | 3489660929 | 1723161600030000000 | 1723161600043617932 | 62292.9 | 0.0 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161600319000000 | 1723161600370793433 | 5000.0 | 2.321 | 0 | 0 | 0.0 |\ | 3489660929 | 1723161600709000000 | 1723161600760777134 | 61659.8 | 0.981 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61631.7 | 0.283 | 0 | 0 | 0.0 |\ | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61632.6 | 0.0 | 0 | 0 | 0.0 |\ \ " \], "text/plain": \[ "shape: (491\_973, 8)\\n", "┌────────────┬─────────────────────┬────────────────────┬─────────┬───────┬──────────┬──────┬──────┐\\n", "│ ev ┆ exch\_ts ┆ local\_ts ┆ px ┆ qty ┆ order\_id ┆ ival ┆ fval │\\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\\n", "│ u64 ┆ i64 ┆ i64 ┆ f64 ┆ f64 ┆ u64 ┆ i64 ┆ f64 │\\n", "╞════════════╪═════════════════════╪════════════════════╪═════════╪═══════╪══════════╪══════╪══════╡\\n", "│ 3758096385 ┆ 1723161256298000000 ┆ 172316125630247151 ┆ 58710.2 ┆ 0.014 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 8 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161256298000000 ┆ 172316125630247151 ┆ 61496.5 ┆ 0.01 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 8 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161256298000000 ┆ 172316125630247151 ┆ 61510.9 ┆ 0.0 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 8 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161256298000000 ┆ 172316125630247151 ┆ 61641.5 ┆ 1.211 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 8 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161256298000000 ┆ 172316125630247151 ┆ 61652.8 ┆ 0.195 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 8 ┆ ┆ ┆ ┆ ┆ │\\n", "│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\\n", "│ 3489660929 ┆ 1723161600030000000 ┆ 172316160004361793 ┆ 62292.9 ┆ 0.0 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 2 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161600319000000 ┆ 172316160037079343 ┆ 5000.0 ┆ 2.321 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 3 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3489660929 ┆ 1723161600709000000 ┆ 172316160076077713 ┆ 61659.8 ┆ 0.981 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 4 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161601054000000 ┆ 172316160110564943 ┆ 61631.7 ┆ 0.283 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 5 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1723161601054000000 ┆ 172316160110564943 ┆ 61632.6 ┆ 0.0 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 5 ┆ ┆ ┆ ┆ ┆ │\\n", "└────────────┴─────────────────────┴────────────────────┴─────────┴───────┴──────────┴──────┴──────┘" \] }, "execution\_count": 3, "metadata": {}, "output\_type": "execute\_result" } \], "source": \[ "import polars as pl\\n", "\\n", "pl.DataFrame(data)" \] }, { "cell\_type": "markdown", "id": "a5ff53f1", "metadata": {}, "source": \[ "You can save the data directly to a file by providing \`output\_filename\`." \] }, { "cell\_type": "code", "execution\_count": 4, "id": "d8b8fd5b", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "Correcting the latency\\n", "local\_timestamp is ahead of exch\_timestamp by 1272156851\\n", "Correcting the event order\\n", "Saving to usdm/btcusdt\_20240808.npz\\n" \] } \], "source": \[ "\_ = binancefutures.convert(\\n", " 'usdm/btcusdt\_20240808.gz',\\n", " output\_filename='usdm/btcusdt\_20240808.npz',\\n", " combined\_stream=True\\n", ")" \] }, { "cell\_type": "markdown", "id": "369d5f17", "metadata": {}, "source": \[ "## Creating a market depth snapshot" \] }, { "cell\_type": "markdown", "id": "04f3f3dd", "metadata": {}, "source": \[ "As Binance Futures exchange runs 24/7, you need the initial snapshot to get the complete(almost) market depth. \\n", "\[Data Collector\](https://github.com/nkaz001/hftbacktest/tree/master/collector) fetches the snapshot only when it makes the connection, so you need build the initial snapshot from the start of the collected feed data." \] }, { "cell\_type": "code", "execution\_count": 5, "id": "3045cb73-ee69-4ef6-89d4-ae754b7132e7", "metadata": {}, "outputs": \[\], "source": \[ "from hftbacktest.data.utils.snapshot import create\_last\_snapshot\\n", "\\n", "# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809.\\n", "data = create\_last\_snapshot(\\n", " \['usdm/btcusdt\_20240808.npz'\],\\n", " tick\_size=0.1,\\n", " lot\_size=0.001\\n", ")" \] }, { "cell\_type": "markdown", "id": "abc51f5f-f568-435d-94a0-3dead470508c", "metadata": {}, "source": \[ "Bid levels are shown before ask levels in the snapshot, and levels are sorted from the best price to the farthest price." \] }, { "cell\_type": "code", "execution\_count": 6, "id": "cc2abfae-8868-4c25-920e-5df1fea0cf53", "metadata": {}, "outputs": \[ { "data": { "text/html": \[ "\ \ \\n", "shape: (9\_597, 8)\ \ | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | 3758096388 | 0 | 0 | 61659.7 | 1.486 | 0 | 0 | 0.0 |\ | 3758096388 | 0 | 0 | 61659.0 | 0.002 | 0 | 0 | 0.0 |\ | 3758096388 | 0 | 0 | 61658.1 | 0.033 | 0 | 0 | 0.0 |\ | 3758096388 | 0 | 0 | 61658.0 | 6.718 | 0 | 0 | 0.0 |\ | 3758096388 | 0 | 0 | 61657.9 | 0.007 | 0 | 0 | 0.0 |\ | … | … | … | … | … | … | … | … |\ | 3489660932 | 0 | 0 | 77354.3 | 0.015 | 0 | 0 | 0.0 |\ | 3489660932 | 0 | 0 | 77905.9 | 0.003 | 0 | 0 | 0.0 |\ | 3489660932 | 0 | 0 | 80000.0 | 10.708 | 0 | 0 | 0.0 |\ | 3489660932 | 0 | 0 | 104765.0 | 0.034 | 0 | 0 | 0.0 |\ | 3489660932 | 0 | 0 | 617050.0 | 0.003 | 0 | 0 | 0.0 |\ \ " \], "text/plain": \[ "shape: (9\_597, 8)\\n", "┌────────────┬─────────┬──────────┬──────────┬────────┬──────────┬──────┬──────┐\\n", "│ ev ┆ exch\_ts ┆ local\_ts ┆ px ┆ qty ┆ order\_id ┆ ival ┆ fval │\\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\\n", "│ u64 ┆ i64 ┆ i64 ┆ f64 ┆ f64 ┆ u64 ┆ i64 ┆ f64 │\\n", "╞════════════╪═════════╪══════════╪══════════╪════════╪══════════╪══════╪══════╡\\n", "│ 3758096388 ┆ 0 ┆ 0 ┆ 61659.7 ┆ 1.486 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3758096388 ┆ 0 ┆ 0 ┆ 61659.0 ┆ 0.002 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3758096388 ┆ 0 ┆ 0 ┆ 61658.1 ┆ 0.033 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3758096388 ┆ 0 ┆ 0 ┆ 61658.0 ┆ 6.718 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3758096388 ┆ 0 ┆ 0 ┆ 61657.9 ┆ 0.007 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\\n", "│ 3489660932 ┆ 0 ┆ 0 ┆ 77354.3 ┆ 0.015 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3489660932 ┆ 0 ┆ 0 ┆ 77905.9 ┆ 0.003 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3489660932 ┆ 0 ┆ 0 ┆ 80000.0 ┆ 10.708 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3489660932 ┆ 0 ┆ 0 ┆ 104765.0 ┆ 0.034 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ 3489660932 ┆ 0 ┆ 0 ┆ 617050.0 ┆ 0.003 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "└────────────┴─────────┴──────────┴──────────┴────────┴──────────┴──────┴──────┘" \] }, "execution\_count": 6, "metadata": {}, "output\_type": "execute\_result" } \], "source": \[ "pl.DataFrame(data)" \] }, { "cell\_type": "code", "execution\_count": 7, "id": "2ba2557f", "metadata": {}, "outputs": \[\], "source": \[ "from hftbacktest.data.utils.snapshot import create\_last\_snapshot\\n", "\\n", "# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809.\\n", "\_ = create\_last\_snapshot(\\n", " \['usdm/btcusdt\_20240808.npz'\],\\n", " tick\_size=0.1,\\n", " lot\_size=0.001,\\n", " output\_snapshot\_filename='usdm/btcusdt\_20240808\_eod.npz'\\n", ")" \] }, { "cell\_type": "code", "execution\_count": 8, "id": "7a74be64-cf31-4c3c-b3df-f7829b4178ed", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "Correcting the latency\\n", "local\_timestamp is ahead of exch\_timestamp by 1273873720\\n", "Correcting the event order\\n", "Saving to usdm/btcusdt\_20240809.npz\\n" \] } \], "source": \[ "# Converts 20240809 data.\\n", "\_ = binancefutures.convert(\\n", " 'usdm/btcusdt\_20240809.gz',\\n", " output\_filename='usdm/btcusdt\_20240809.npz',\\n", " combined\_stream=True\\n", ")\\n", "\\n", "# Builds 20240809's last snapshot.\\n", "# Due to the file size limitation of GitHub, btcusdt\_20240809.npz does not contain data for the entire day.\\n", "\_ = create\_last\_snapshot(\\n", " \['usdm/btcusdt\_20240809.npz'\],\\n", " tick\_size=0.1,\\n", " lot\_size=0.001,\\n", " output\_snapshot\_filename='usdm/btcusdt\_20240809\_last.npz',\\n", " initial\_snapshot='usdm/btcusdt\_20240808\_eod.npz',\\n", ")" \] }, { "cell\_type": "code", "execution\_count": 9, "id": "9b213182-b619-46be-a39e-eec99de7b988", "metadata": {}, "outputs": \[\], "source": \[ "# Builds 20240809's last snapshot without the initial snapshot.\\n", "\_ = create\_last\_snapshot(\\n", " \['usdm/btcusdt\_20240809.npz'\],\\n", " tick\_size=0.1,\\n", " lot\_size=0.001,\\n", " output\_snapshot\_filename='usdm/btcusdt\_20240809\_last\_wo\_ss.npz'\\n", ")\\n", "\\n", "# Builds the 20240809's last snapshot from 20240808 without the initial snapshot.\\n", "\_ = create\_last\_snapshot(\\n", " \[\\n", " 'usdm/btcusdt\_20240808.npz',\\n", " 'usdm/btcusdt\_20240809.npz'\\n", " \],\\n", " tick\_size=0.1,\\n", " lot\_size=0.001,\\n", " output\_snapshot\_filename='usdm/btcusdt\_20240809\_last.npz'\\n", ")" \] }, { "cell\_type": "markdown", "id": "63f79c14", "metadata": {}, "source": \[ "## Getting started from Tardis.dev data\\n", "\\n", "Few vendors offer tick-by-tick full market depth data along with snapshot and trade data, and Tardis.dev is among them.\\n", "\\n", "\ \ \\n", " \\n", "\*\*Note:\*\* Some data may have an issue with the exchange timestamp. Ideally, the exchange timestamp should reflect the moment the event occurs at the matching engine. However, some data uses the server's data sent timestamp instead of the matching engine timestamp.\\n", "\\n", "\ \ " \] }, { "cell\_type": "code", "execution\_count": 10, "id": "016cbeb0-808a-4aaf-a18a-88e885161841", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "--2024-08-09 09:42:51-- https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz\\n", "Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.6.96, 104.18.7.96, 2606:4700::6812:760, ...\\n", "Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.6.96|:443... connected.\\n", "HTTP request sent, awaiting response... 200 OK\\n", "Length: 3090479 (2.9M) \[text/csv\]\\n", "Saving to: ‘BTCUSDT\_trades.csv.gz’\\n", "\\n", "BTCUSDT\_trades.csv. 100%\[===================>\] 2.95M 5.66MB/s in 0.5s \\n", "\\n", "2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\]\\n", "\\n", "--2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz\\n", "Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ...\\n", "Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected.\\n", "HTTP request sent, awaiting response... 200 OK\\n", "Length: 250016849 (238M) \[text/csv\]\\n", "Saving to: ‘BTCUSDT\_book.csv.gz’\\n", "\\n", "BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s \\n", "\\n", "2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\]\\n", "\\n" \] } \], "source": \[ "# https://docs.tardis.dev/historical-data-details/binance-futures\\n", "\\n", "# Downloads sample Binance futures BTCUSDT trades\\n", "!wget https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz -O BTCUSDT\_trades.csv.gz\\n", " \\n", "# Downloads sample Binance futures BTCUSDT book\\n", "!wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz -O BTCUSDT\_book.csv.gz" \] }, { "cell\_type": "markdown", "id": "2d16bdd3-2843-457c-ac20-680b27b76692", "metadata": {}, "source": \[ "It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp." \] }, { "cell\_type": "code", "execution\_count": 11, "id": "2a94dc09", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "Reading BTCUSDT\_trades.csv.gz\\n", "Reading BTCUSDT\_book.csv.gz\\n", "Correcting the latency\\n", "Correcting the event order\\n" \] } \], "source": \[ "from hftbacktest.data.utils import tardis\\n", "\\n", "data = tardis.convert(\\n", " \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\]\\n", ")" \] }, { "cell\_type": "code", "execution\_count": 12, "id": "4ee3977f-0333-49e8-9a25-f10c4f5fd856", "metadata": {}, "outputs": \[ { "data": { "text/html": \[ "\ \ \\n", "shape: (27\_532\_602, 8)\ \ | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 |\ | --- | --- | --- | --- | --- | --- | --- | --- |\ | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 |\ | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 |\ | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 |\ | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 |\ | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 |\ | … | … | … | … | … | … | … | … |\ | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 |\ | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 |\ | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 |\ | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 |\ | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 |\ \ " \], "text/plain": \[ "shape: (27\_532\_602, 8)\\n", "┌────────────┬─────────────────────┬────────────────────┬─────────┬───────┬──────────┬──────┬──────┐\\n", "│ ev ┆ exch\_ts ┆ local\_ts ┆ px ┆ qty ┆ order\_id ┆ ival ┆ fval │\\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\\n", "│ u64 ┆ i64 ┆ i64 ┆ f64 ┆ f64 ┆ u64 ┆ i64 ┆ f64 │\\n", "╞════════════╪═════════════════════╪════════════════════╪═════════╪═══════╪══════════╪══════╪══════╡\\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249705200 ┆ 9364.51 ┆ 1.197 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249734600 ┆ 9365.67 ┆ 0.02 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249735200 ┆ 9365.86 ┆ 0.01 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249735700 ┆ 9366.36 ┆ 0.002 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096386 ┆ 1580515202342000000 ┆ 158051520249736300 ┆ 9366.36 ┆ 0.003 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\\n", "│ 3489660929 ┆ 1580601599812000000 ┆ 158060159994440400 ┆ 9397.79 ┆ 0.0 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1580601599826000000 ┆ 158060159995217600 ┆ 9354.8 ┆ 4.07 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1580601599836000000 ┆ 158060159996296100 ┆ 9351.47 ┆ 3.914 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3489660929 ┆ 1580601599836000000 ┆ 158060159996346100 ┆ 9397.78 ┆ 0.1 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "│ 3758096385 ┆ 1580601599848000000 ┆ 158060159997364700 ┆ 9348.14 ┆ 3.98 ┆ 0 ┆ 0 ┆ 0.0 │\\n", "│ ┆ ┆ 0 ┆ ┆ ┆ ┆ ┆ │\\n", "└────────────┴─────────────────────┴────────────────────┴─────────┴───────┴──────────┴──────┴──────┘" \] }, "execution\_count": 12, "metadata": {}, "output\_type": "execute\_result" } \], "source": \[ "pl.DataFrame(data)" \] }, { "cell\_type": "markdown", "id": "b863d7cb", "metadata": {}, "source": \[ "You can save the data directly to a file by providing \`output\_filename\`. If there are too many rows, you need to increase \`buffer\_size\`. " \] }, { "cell\_type": "code", "execution\_count": 13, "id": "f2026ce5", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "Reading BTCUSDT\_trades.csv.gz\\n", "Reading BTCUSDT\_book.csv.gz\\n", "Correcting the latency\\n", "Correcting the event order\\n", "Saving to btcusdt\_20200201.npz\\n" \] } \], "source": \[ "\_ = tardis.convert(\\n", " \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\],\\n", " output\_filename='btcusdt\_20200201.npz',\\n", " buffer\_size=200\_000\_000\\n", ")" \] }, { "cell\_type": "markdown", "id": "736272d6", "metadata": {}, "source": \[ "Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don't need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev's SOD snapshot in the converted file using the option." \] } \], "metadata": { "kernelspec": { "display\_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language\_info": { "codemirror\_mode": { "name": "ipython", "version": 3 }, "file\_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert\_exporter": "python", "pygments\_lexer": "ipython3", "version": "3.10.14" } }, "nbformat": 4, "nbformat\_minor": 5 } --- # Data Preparation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Data Preparation * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Data%20Preparation.ipynb.txt) * * * Data Preparation[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Data%20Preparation.html#Data-Preparation "Link to this heading") =============================================================================================================================================== To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself. Getting started from Binance Futures’ raw feed data[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Data%20Preparation.html#Getting-started-from-Binance-Futures'-raw-feed-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can collect Binance Futures feed yourself using [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) . \[1\]: import gzip with gzip.open('usdm/btcusdt\_20240808.gz', 'r') as f: for i in range(5): line \= f.readline() print(line) b'1723161255030314667 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1723161256299,"T":1723161256298,"s":"BTCUSDT","U":5123107832006,"u":5123107837557,"pu":5123107831937,"b":\[\["58710.20","0.014"\],\["61496.50","0.010"\],\["61510.90","0.000"\],\["61641.50","1.211"\],\["61652.80","0.195"\],\["61653.30","0.072"\],\["61653.70","0.067"\],\["61657.90","0.067"\],\["61668.50","0.086"\],\["61670.60","0.161"\],\["61672.50","0.821"\],\["61673.60","0.048"\],\["61675.60","0.050"\],\["61684.50","0.765"\],\["61686.20","0.008"\],\["61701.80","0.331"\],\["61703.10","0.238"\],\["61715.90","0.308"\],\["61721.60","0.235"\],\["61724.10","0.002"\],\["61737.00","0.015"\],\["61739.00","0.000"\],\["61740.10","0.008"\],\["61740.50","12.111"\],\["61756.90","0.550"\],\["61758.70","0.003"\],\["61763.20","0.014"\],\["61764.10","0.168"\],\["61764.30","0.000"\],\["61765.50","0.000"\],\["61767.40","0.004"\],\["61768.20","0.120"\],\["61768.60","0.020"\],\["61768.90","0.099"\],\["61770.80","0.049"\],\["61771.10","0.612"\],\["61771.70","0.010"\],\["61773.50","0.035"\],\["61773.80","0.025"\],\["61774.00","0.112"\],\["61775.60","0.010"\],\["61776.00","0.084"\],\["61778.30","0.000"\],\["61778.60","0.408"\],\["61779.30","0.020"\],\["61779.60","0.220"\],\["61783.80","0.002"\],\["61784.90","0.102"\],\["61785.00","0.000"\],\["61788.10","0.140"\],\["61789.50","0.000"\],\["61798.70","0.153"\],\["61800.20","2.507"\]\],"a":\[\["61800.30","3.330"\],\["61804.60","0.057"\],\["61810.00","0.285"\],\["61812.00","0.732"\],\["61814.90","0.000"\],\["61817.20","0.000"\],\["61818.70","0.040"\],\["61824.00","0.860"\],\["61829.10","0.185"\],\["61831.30","0.008"\],\["61831.40","0.501"\],\["61839.00","0.002"\],\["61840.00","0.192"\],\["61856.30","0.003"\],\["61857.10","0.027"\],\["61857.40","0.000"\],\["61858.80","0.005"\],\["61858.90","0.032"\],\["61859.60","0.034"\],\["61874.80","0.006"\],\["61893.40","0.335"\],\["61911.90","0.014"\],\["61925.90","0.000"\],\["61930.50","0.015"\],\["61945.10","0.000"\],\["61953.70","0.000"\],\["62144.00","0.006"\],\["63113.70","0.000"\],\["65880.70","15.918"\]\]}}\\n' b'1723161255088169167 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107839020,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.510","T":1723161256313,"E":1723161256313}}\\n' b'1723161255088176367 {"stream":"btcusdt@trade","data":{"e":"trade","E":1723161256322,"T":1723161256322,"s":"BTCUSDT","t":5266583935,"p":"61800.30","q":"0.006","X":"MARKET","m":false}}\\n' b'1723161255088181667 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840008,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.504","T":1723161256322,"E":1723161256322}}\\n' b'1723161255088182467 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840016,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.522","T":1723161256322,"E":1723161256322}}\\n' The first token of the line is timestamp received by local. **Note:** The timestamp is in nanoseconds. The data needs to be converted to normalized data that can be fed into HftBacktest. `convert` method also attempts to correct timestamps by reordering the rows. \[2\]: import numpy as np from hftbacktest.data.utils import binancefutures data \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Normalized data as follows. You can find more details on [Data](https://github.com/nkaz001/hftbacktest/wiki/Data) . \[3\]: import polars as pl pl.DataFrame(data) \[3\]: shape: (491\_973, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 58710.2 | 0.014 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61496.5 | 0.01 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61510.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61641.5 | 1.211 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61652.8 | 0.195 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1723161600030000000 | 1723161600043617932 | 62292.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161600319000000 | 1723161600370793433 | 5000.0 | 2.321 | 0 | 0 | 0.0 | | 3489660929 | 1723161600709000000 | 1723161600760777134 | 61659.8 | 0.981 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61631.7 | 0.283 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61632.6 | 0.0 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. \[4\]: \_ \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', output\_filename\='usdm/btcusdt\_20240808.npz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Saving to usdm/btcusdt\_20240808.npz Creating a market depth snapshot[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Data%20Preparation.html#Creating-a-market-depth-snapshot "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As Binance Futures exchange runs 24/7, you need the initial snapshot to get the complete(almost) market depth. [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) fetches the snapshot only when it makes the connection, so you need build the initial snapshot from the start of the collected feed data. \[5\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. data \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001 ) Bid levels are shown before ask levels in the snapshot, and levels are sorted from the best price to the farthest price. \[6\]: pl.DataFrame(data) \[6\]: shape: (9\_597, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096388 | 0 | 0 | 61659.7 | 1.486 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61659.0 | 0.002 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.1 | 0.033 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.0 | 6.718 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61657.9 | 0.007 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660932 | 0 | 0 | 77354.3 | 0.015 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 77905.9 | 0.003 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 80000.0 | 10.708 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 104765.0 | 0.034 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 617050.0 | 0.003 | 0 | 0 | 0.0 | \[7\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240808\_eod.npz' ) \[8\]: \# Converts 20240809 data. \_ \= binancefutures.convert( 'usdm/btcusdt\_20240809.gz', output\_filename\='usdm/btcusdt\_20240809.npz', combined\_stream\=True ) \# Builds 20240809's last snapshot. \# Due to the file size limitation of GitHub, btcusdt\_20240809.npz does not contain data for the entire day. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz', initial\_snapshot\='usdm/btcusdt\_20240808\_eod.npz', ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1273873720 Correcting the event order Saving to usdm/btcusdt\_20240809.npz \[9\]: \# Builds 20240809's last snapshot without the initial snapshot. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last\_wo\_ss.npz' ) \# Builds the 20240809's last snapshot from 20240808 without the initial snapshot. \_ \= create\_last\_snapshot( \[\ 'usdm/btcusdt\_20240808.npz',\ 'usdm/btcusdt\_20240809.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz' ) Getting started from Tardis.dev data[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Data%20Preparation.html#Getting-started-from-Tardis.dev-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Few vendors offer tick-by-tick full market depth data along with snapshot and trade data, and Tardis.dev is among them. **Note:** Some data may have an issue with the exchange timestamp. Ideally, the exchange timestamp should reflect the moment the event occurs at the matching engine. However, some data uses the server’s data sent timestamp instead of the matching engine timestamp. \[10\]: \# https://docs.tardis.dev/historical-data-details/binance-futures \# Downloads sample Binance futures BTCUSDT trades !wget https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_trades.csv.gz \# Downloads sample Binance futures BTCUSDT book !wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_book.csv.gz \--2024-08-09 09:42:51-- https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.6.96, 104.18.7.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.6.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3090479 (2.9M) \[text/csv\] Saving to: ‘BTCUSDT\_trades.csv.gz’ BTCUSDT\_trades.csv. 100%\[===================>\] 2.95M 5.66MB/s in 0.5s 2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s 2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp. \[11\]: from hftbacktest.data.utils import tardis data \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\] ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order \[12\]: pl.DataFrame(data) \[12\]: shape: (27\_532\_602, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 | | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 | | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 | | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: \_ \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000 ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don’t need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev’s SOD snapshot in the converted file using the option. --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Index * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#M) | [**N**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#N) | [**O**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#P) | [**Q**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#Q) | [**R**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#T) | [**U**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#U) | [**V**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/py-v2.1.0/genindex.html#W) A - | | | | --- | --- | | * [ALL\_ASSETS (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.ALL_ASSETS)

* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency)

* [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size) | * [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.databento)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_depth() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_depth)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency) | * [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FILLED)

* [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FOK) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.databento
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [l2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.l2)

* [l3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.l3)

* [l3\_fifo\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.l3_fifo_queue_model)

* [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [latency\_offset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.latency_offset)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.LIMIT) | * [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset)

* [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord)

* [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.databento](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [parallel\_load() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.parallel_load)

* [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.price_tick) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD) | * [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade)

* [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats) | * [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Permalink to this heading") ============================================================================================================================================================================================================================================================================================================================== For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Getting Started — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Getting Started * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Getting%20Started.ipynb.txt) * * * Getting Started[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Getting-Started "Link to this heading") ============================================================================================================================================ Printing the best bid and the best ask[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Printing-the-best-bid-and-the-best-ask "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[1\]: from numba import njit import numpy as np \# numba.njit is strongly recommended for fast backtesting. @njit def print\_bbo(hbt): \# Iterating until hftbacktest reaches the end of data. \# Elapses 60-sec every iteration. \# Time unit is the same as data's timestamp's unit. \# Timestamp of the sample data is in nanoseconds. while hbt.elapse(60 \* 1e9) \== 0: \# Gets the market depth for the first asset. depth \= hbt.depth(0) \# Prints the best bid and the best offer. print( 'current\_timestamp:', hbt.current\_timestamp, ', best\_bid:', np.round(depth.best\_bid, 1), ', best\_ask:', np.round(depth.best\_ask, 1) ) return True \[2\]: from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest asset \= ( BacktestAsset() \# Sets the data to feed for this asset. # \# Due to the vast size of tick-by-tick market depth and trade data, \# loading the entire dataset into memory can be challenging, \# particularly when backtesting across multiple days. \# HftBacktest offers lazy loading support and is compatible with npy and preferably npz. # \# For details on the normalized feed data, refer to the following documents. \# \* https://hftbacktest.readthedocs.io/en/latest/data.html \# \* https://hftbacktest.readthedocs.io/en/latest/tutorials/Data%20Preparation.html .data(\['usdm/btcusdt\_20240809.npz'\]) \# Sets the initial snapshot (optional). .initial\_snapshot('usdm/btcusdt\_20240808\_eod.npz') \# Asset type: \# \* Linear \# \* Inverse. \# 1.0 represents the contract size, which is the value of the asset per quoted price. .linear\_asset(1.0) \# HftBacktest provides two built-in latency models. \# \* constant\_latency \# \* intp\_order\_latency \# To implement your own latency model, please use Rust. # \# Time unit is the same as data's timestamp's unit. Timestamp of the sample data is in nanoseconds. \# Sets the order entry latency and response latency to 10ms. .constant\_latency(10\_000\_000, 10\_000\_000) \# HftBacktest provides several types of built-in queue position models. \# Please find the details in the documents below. \# https://hftbacktest.readthedocs.io/en/latest/tutorials/Probability%20Queue%20Models.html # \# To implement your own queue position model, please use Rust. .risk\_adverse\_queue\_model() \# HftBacktest provides two built-in exchange models. \# \* no\_partial\_fill\_exchange \# \* partial\_fill\_exchange \# To implement your own exchange model, please use Rust. .no\_partial\_fill\_exchange() \# HftBacktest provides several built-in fee models. \# \* trading\_value\_fee\_model \# \* trading\_qty\_fee\_model \# \* flat\_per\_trade\_fee\_model # \# 0.02% maker fee and 0.07% taker fee. If the fee is negative, it represents a rebate. \# For example, -0.00005 represents a 0.005% rebate for the maker order. .trading\_value\_fee\_model(0.0002, 0.0007) \# Tick size of this asset: minimum price increasement .tick\_size(0.1) \# Lot size of this asset: minimum trading unit. .lot\_size(0.001) \# Sets the capacity of the vector that stores trades occurring in the market. \# If you set the size, you need call \`clear\_last\_trades\` to clear the vector. \# A value of 0 indicates that no market trades are stored. (Default) .last\_trades\_capacity(0) ) \# HftBacktest provides several types of built-in market depth implementations. \# HashMapMarketDepthBacktest constructs a Backtest using a HashMap-based market depth implementation. \# Another useful implementation is ROIVectorMarketDepth, which is utilized in ROIVectorMarketDepthBacktest. \# Please find the details in the document below. hbt \= HashMapMarketDepthBacktest(\[asset\]) You can see the best bid and best ask every 60 seconds. Since the price is a 32-bit float, there may be floating-point errors. Be careful when using it. In the example, for readability, the price is rounded based on the tick size. \[3\]: print\_bbo(hbt) current\_timestamp: 1723161661500000000 , best\_bid: 61594.1 , best\_ask: 61594.2 current\_timestamp: 1723161721500000000 , best\_bid: 61576.5 , best\_ask: 61576.6 current\_timestamp: 1723161781500000000 , best\_bid: 61629.6 , best\_ask: 61629.7 current\_timestamp: 1723161841500000000 , best\_bid: 61621.5 , best\_ask: 61621.6 current\_timestamp: 1723161901500000000 , best\_bid: 61583.9 , best\_ask: 61584.0 \[3\]: True HftBacktest cannot be reused. Therefore, after using the backtest, make sure to close it. If you use the backtest after closing, it will crash. \[4\]: \_ \= hbt.close() Feeding the data[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Feeding-the-data "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------- When you possess adequate memory, preloading the data into memory and providing it as input will be more efficient than lazy-loading during repeated backtesting. \[5\]: btcusdt\_20230809 \= np.load('usdm/btcusdt\_20240809.npz')\['data'\] btcusdt\_20230808\_eod \= np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\] asset \= ( BacktestAsset() .data(\[btcusdt\_20230809\]) .initial\_snapshot(btcusdt\_20230808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) \[6\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_bbo(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , best\_bid: 61594.1 , best\_ask: 61594.2 current\_timestamp: 1723161721500000000 , best\_bid: 61576.5 , best\_ask: 61576.6 current\_timestamp: 1723161781500000000 , best\_bid: 61629.6 , best\_ask: 61629.7 current\_timestamp: 1723161841500000000 , best\_bid: 61621.5 , best\_ask: 61621.6 current\_timestamp: 1723161901500000000 , best\_bid: 61583.9 , best\_ask: 61584.0 Getting the market depth[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Getting-the-market-depth "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def print\_3depth(hbt): while hbt.elapse(60 \* 1e9) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = price / tick\_size i \= 0 for tick\_price in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): qty \= depth.ask\_qty\_at\_tick(tick\_price) if qty \> 0: print( 'ask: ', qty, '@', np.round(tick\_price \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for tick\_price in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): qty \= depth.bid\_qty\_at\_tick(tick\_price) if qty \> 0: print( 'bid: ', qty, '@', np.round(tick\_price \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[8\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_3depth(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Submitting an order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Submitting-an-order "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------- \[9\]: from hftbacktest import LIMIT, GTC, NONE, NEW, FILLED, CANCELED, EXPIRED @njit def print\_orders(hbt): \# You can access open orders and also closed orders via hbt.orders. \# Gets the OrderDict for the first asset. orders \= hbt.orders(0) \# hbt.orders is a dictionary, but be aware that it does not support all dict methods, and its keys are order\_id (int). order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' \# Exchange hasn't received an order yet. elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order.order\_id, ', order\_price:', np.round(order.price, 1), ', order\_qty:', order.qty, ', order\_status:', order\_status, ', order\_req:', order\_req ) @njit def submit\_order(hbt): is\_order\_submitted \= False while hbt.elapse(30 \* 1e9) \== 0: \# Prints open orders. print\_orders(hbt) depth \= hbt.depth(0) if not is\_order\_submitted: \# Submits a buy order at 300 ticks below the best bid for the first asset. order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC \# Good 'till cancel order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True return True \[10\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161691500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161751500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161781500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161811500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161841500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161871500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161901500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE Clearing inactive orders (FILLED, CANCELED, EXPIRED)[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Clearing-inactive-orders-(FILLED,-CANCELED,-EXPIRED) "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[11\]: from hftbacktest import GTC @njit def clear\_inactive\_orders(hbt): is\_order\_submitted \= False while hbt.elapse(30 \* 1e9) \== 0: print\_orders(hbt) \# Removes inactive(FILLED, CANCELED, EXPIRED) orders from hbt.orders for the first asset. hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True return True \[12\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) clear\_inactive\_orders(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , order\_id: 1 , order\_price: 61643.8 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE Watching a order status - pending due to order latency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Watching-a-order-status---pending-due-to-order-latency "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[13\]: from hftbacktest import GTC @njit def watch\_pending(hbt): is\_order\_submitted \= False \# Elapses 0.01-sec every iteration. while hbt.elapse(0.01 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid \- 300 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) is\_order\_submitted \= True \# Prevents too many prints orders \= hbt.orders(0) order \= orders.get(order\_id) if order.status \== NEW: return False return True The `order_status` is `None` until the acceptance message is received. \[14\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) watch\_pending(hbt) \_ \= hbt.close() current\_timestamp: 1723161601520000000 , order\_id: 1 , order\_price: 61629.7 , order\_qty: 1.0 , order\_status: NONE , order\_req: NEW current\_timestamp: 1723161601530000000 , order\_id: 1 , order\_price: 61629.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE Waiting for an order response[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Waiting-for-an-order-response "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[15\]: from hftbacktest import GTC @njit def wait\_for\_order\_response(hbt): order\_id \= 0 is\_order\_submitted \= False while hbt.elapse(0.01 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Prevents too many prints orders \= hbt.orders(0) if order\_id in orders: if orders.get(order\_id).status \== NEW: return False depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Waits for the order response for a given order id for the first asset. print('an order is submitted at', hbt.current\_timestamp) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1 \* 1e9) print('an order response is received at', hbt.current\_timestamp) is\_order\_submitted \= True return True Since the `ConstantLatency` model is used, the round-trip latency is exactly 200ms. Ideally, using historical order latency data collected from the live market is the best approach. However, if this data is not available, starting with artificially generated order latency based on feed latency is another option. We will explore this in the following examples. \[16\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) wait\_for\_order\_response(hbt) \_ \= hbt.close() an order is submitted at 1723161601510000000 an order response is received at 1723161601530000000 current\_timestamp: 1723161601540000000 , order\_id: 1 , order\_price: 61659.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE Printing position, balance, fee, and equity[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Printing-position,-balance,-fee,-and-equity "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[17\]: @njit def position(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Prints position print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', hbt.state\_values(0).balance, ', fee:', hbt.state\_values(0).fee ) depth \= hbt.depth(0) if not is\_order\_submitted: order\_id \= 1 order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True return True \[18\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) position(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161781520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161841520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 current\_timestamp: 1723161901520000000 , position: 1.0 , balance: -61594.100000000006 , fee: 12.318820000000002 Canceling an open order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Canceling-an-open-order "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ \[19\]: @njit def submit\_and\_cancel\_order(hbt): is\_order\_submitted \= False while hbt.elapse(0.1 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) \# Cancels if there is an open order orders \= hbt.orders(0) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() \# an order is only cancellable if order status is NEW. \# cancel request is negated if the order is already filled or filled before cancel request is processed. if order.cancellable: hbt.cancel(0, order.order\_id, False) \# You can see status still NEW and see req CANCEL. print\_orders(hbt) \# cancels request also has order entry/response latencies the same as submitting. hbt.wait\_order\_response(0, order.order\_id, 1e9) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 order\_price \= depth.best\_bid \- 100 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTC order\_type \= LIMIT hbt.submit\_buy\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) \# Timeout is set 1-second. hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True else: if len(hbt.orders(0)) \== 0: return False return True \[20\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_and\_cancel\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161601720000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE current\_timestamp: 1723161601720000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: NEW , order\_req: CANCEL current\_timestamp: 1723161601840000000 , order\_id: 1 , order\_price: 61649.7 , order\_qty: 1.0 , order\_status: CANCELED , order\_req: NONE Market order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Market-order "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------- \[21\]: from hftbacktest import MARKET @njit def print\_orders\_exec\_price(hbt): orders \= hbt.orders(0) order\_values \= orders.values() while order\_values.has\_next(): order \= order\_values.get() order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order.order\_id, ', order\_price:', np.round(order.price, 1), ', order\_qty:', order.qty, ', order\_status:', order\_status, ', exec\_price:', np.round(order.exec\_price, 1) ) @njit def market\_order(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) state\_values \= hbt.state\_values(0) print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', state\_values.balance, ', fee:', state\_values.fee ) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 \# Sets an arbitrary price, which does not affect MARKET orders. order\_price \= depth.best\_bid order\_qty \= 1 time\_in\_force \= GTC order\_type \= MARKET hbt.submit\_sell\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) hbt.wait\_order\_response(0, order\_id, 1e9) \# You can see the order immediately filled. \# Also you can see the order executed at the best bid which is different from what it was submitted at. print('best\_bid:', depth.best\_bid) print\_orders\_exec\_price(hbt) is\_order\_submitted \= True return True \[22\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) market\_order(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 best\_bid: 61594.100000000006 current\_timestamp: 1723161661520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , exec\_price: 61594.1 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61594.1 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161781520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161841520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 current\_timestamp: 1723161901520000000 , position: -1.0 , balance: 61594.100000000006 , fee: 43.11587 GTX, Post-Only order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#GTX,-Post-Only-order "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------ \[23\]: from hftbacktest import GTX @njit def submit\_gtx(hbt): is\_order\_submitted \= False while hbt.elapse(60 \* 1e9) \== 0: print\_orders(hbt) hbt.clear\_inactive\_orders(0) state\_values \= hbt.state\_values(0) print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position(0), ', balance:', state\_values.balance, ', fee:', state\_values.fee ) if not is\_order\_submitted: depth \= hbt.depth(0) order\_id \= 1 \# Sets a deep price in the opposite side and it will be rejected by GTX. order\_price \= depth.best\_bid \- 100 \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_sell\_order(0, order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) hbt.wait\_order\_response(0, order\_id, 1e9) is\_order\_submitted \= True return True \[24\]: hbt \= HashMapMarketDepthBacktest(\[asset\]) submit\_gtx(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161721520000000 , order\_id: 1 , order\_price: 61584.1 , order\_qty: 1.0 , order\_status: EXPIRED , order\_req: NONE current\_timestamp: 1723161721520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161781520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161841520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 current\_timestamp: 1723161901520000000 , position: 0.0 , balance: 0.0 , fee: 0.0 Plotting BBO[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Plotting-BBO "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------- \[25\]: @njit def plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask): while hbt.elapse(1 \* 1e9) \== 0: \# Records data points local\_timestamp.append(hbt.current\_timestamp) depth \= hbt.depth(0) best\_bid.append(depth.best\_bid) best\_ask.append(depth.best\_ask) return True \[26\]: \# Uses Numba list for njit. from numba.typed import List from numba import int64, float64 import polars as pl local\_timestamp \= List.empty\_list(int64, allocated\=10000) best\_bid \= List.empty\_list(float64, allocated\=10000) best\_ask \= List.empty\_list(float64, allocated\=10000) hbt \= HashMapMarketDepthBacktest(\[asset\]) plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask) hbt.close() df \= pl.DataFrame({'timestamp': local\_timestamp, 'best\_bid': best\_bid, 'best\_ask': best\_ask}) df \= df.with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns') ) df.plot(x\='timestamp') \[26\]: Printing stats[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Getting%20Started.html#Printing-stats "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------ \[27\]: @njit def submit\_order\_stats(hbt, recorder): buy\_order\_id \= 1 sell\_order\_id \= 2 half\_spread \= 5 \* hbt.depth(0).tick\_size while hbt.elapse(1 \* 1e9) \== 0: hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 if buy\_order\_id not in hbt.orders(0): order\_price \= round((mid\_price \- half\_spread) / depth.tick\_size) \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_buy\_order(0, buy\_order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) else: hbt.cancel(0, buy\_order\_id, False) if sell\_order\_id not in hbt.orders(0): order\_price \= round((mid\_price + half\_spread) / depth.tick\_size) \* depth.tick\_size order\_qty \= 1 time\_in\_force \= GTX order\_type \= LIMIT hbt.submit\_sell\_order(0, sell\_order\_id, order\_price, order\_qty, time\_in\_force, order\_type, False) else: hbt.cancel(0, sell\_order\_id, False) recorder.record(hbt) return True \[28\]: from hftbacktest import Recorder hbt \= HashMapMarketDepthBacktest(\[asset\]) recorder \= Recorder( \# The number of assets hbt.num\_assets, \# The buffer size for records 1000000 ) submit\_order\_stats(hbt, recorder.recorder) \_ \= hbt.close() You can get recorded states using the `get` method with the asset number. \[29\]: recorder.get(0) \[29\]: array(\[(1723161602500000000, 61659.85, 0., 0.000000e+00, 0. , 0, 0., 0. ),\ (1723161603500000000, 61659.95, 1., -6.165940e+04, 12.33188, 1, 1., 61659.4),\ (1723161604500000000, 61670.85, 1., -6.165940e+04, 12.33188, 1, 1., 61659.4),\ (1723161605500000000, 61692.45, 0., 1.200000e+01, 24.66616, 2, 2., 123330.8),\ (1723161606500000000, 61693.95, 0., 1.300000e+01, 49.34312, 4, 4., 246715.6),\ (1723161607500000000, 61695.45, -1., 6.170740e+04, 61.682 , 5, 5., 308410. ),\ (1723161608500000000, 61709.95, -2., 1.234033e+05, 74.02118, 6, 6., 370105.9),\ (1723161609500000000, 61707.35, -1., 6.169390e+04, 86.36306, 7, 7., 431815.3),\ (1723161610500000000, 61715.85, -1., 6.169390e+04, 86.36306, 7, 7., 431815.3),\ (1723161611500000000, 61711.85, -2., 1.234103e+05, 98.70634, 8, 8., 493531.7),\ (1723161612500000000, 61713.95, -3., 1.851227e+05, 111.04882, 9, 9., 555244.1),\ (1723161613500000000, 61706.15, -4., 2.468371e+05, 123.3917 , 10, 10., 616958.5),\ (1723161614500000000, 61708.25, -5., 3.085437e+05, 135.73302, 11, 11., 678665.1),\ (1723161615500000000, 61699.75, -6., 3.702525e+05, 148.07478, 12, 12., 740373.9),\ (1723161616500000000, 61700.95, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161617500000000, 61698.05, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161618500000000, 61706.95, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161619500000000, 61695.85, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161620500000000, 61713.45, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161621500000000, 61707.65, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161622500000000, 61713.45, -7., 4.319527e+05, 160.41482, 13, 13., 802074.1),\ (1723161623500000000, 61704.05, -6., 3.702455e+05, 172.75626, 14, 14., 863781.3),\ (1723161624500000000, 61702.45, -5., 3.085419e+05, 185.09698, 15, 15., 925484.9),\ (1723161625500000000, 61704.65, -6., 3.702448e+05, 197.43756, 16, 16., 987187.8),\ (1723161626500000000, 61704.65, -6., 3.702448e+05, 197.43756, 16, 16., 987187.8),\ (1723161627500000000, 61695.35, -5., 3.085406e+05, 209.7784 , 17, 17., 1048892. ),\ (1723161628500000000, 61693.75, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161629500000000, 61693.75, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161630500000000, 61682.35, -4., 2.468458e+05, 222.11736, 18, 18., 1110586.8),\ (1723161631500000000, 61673.85, -3., 1.851640e+05, 234.45372, 19, 19., 1172268.6),\ (1723161632500000000, 61666.05, -2., 1.234906e+05, 246.7884 , 20, 20., 1233942. ),\ (1723161633500000000, 61671.05, -2., 1.234906e+05, 246.7884 , 20, 20., 1233942. ),\ (1723161634500000000, 61673.75, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161635500000000, 61673.75, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161636500000000, 61666.05, -3., 1.851622e+05, 259.12272, 21, 21., 1295613.6),\ (1723161637500000000, 61670.45, -4., 2.468288e+05, 271.45604, 22, 22., 1357280.2),\ (1723161638500000000, 61664.05, -4., 2.468288e+05, 271.45604, 22, 22., 1357280.2),\ (1723161639500000000, 61649.05, -3., 1.851652e+05, 283.78876, 23, 23., 1418943.8),\ (1723161640500000000, 61645.05, -3., 1.851652e+05, 283.78876, 23, 23., 1418943.8),\ (1723161641500000000, 61640.05, -2., 1.235206e+05, 296.11768, 24, 24., 1480588.4),\ (1723161642500000000, 61638.45, -1., 6.188100e+04, 308.4456 , 25, 25., 1542228. ),\ (1723161643500000000, 61636.05, 0., 2.431000e+02, 320.77318, 26, 26., 1603865.9),\ (1723161644500000000, 61641.95, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161645500000000, 61641.95, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161646500000000, 61644.35, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161647500000000, 61636.45, -1., 6.187970e+04, 333.1005 , 27, 27., 1665502.5),\ (1723161648500000000, 61630.05, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161649500000000, 61630.05, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161650500000000, 61631.65, 0., 2.438000e+02, 345.42768, 28, 28., 1727138.4),\ (1723161651500000000, 61639.05, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161652500000000, 61632.05, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161653500000000, 61633.95, -1., 6.187600e+04, 357.75412, 29, 29., 1788770.6),\ (1723161654500000000, 61632.05, -2., 1.235104e+05, 370.081 , 30, 30., 1850405. ),\ (1723161655500000000, 61604.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161656500000000, 61604.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161657500000000, 61607.05, -1., 6.187880e+04, 382.40732, 31, 31., 1912036.6),\ (1723161658500000000, 61603.15, 0., 2.722000e+02, 394.72864, 32, 32., 1973643.2),\ (1723161659500000000, 61601.15, 1., -6.133040e+04, 407.04916, 33, 33., 2035245.8),\ (1723161660500000000, 61595.35, 2., -1.229310e+05, 419.36928, 34, 34., 2096846.4),\ (1723161661500000000, 61594.15, 3., -1.845258e+05, 431.68824, 35, 35., 2158441.2),\ (1723161662500000000, 61578.15, 4., -2.461194e+05, 444.00696, 36, 36., 2220034.8),\ (1723161663500000000, 61565.25, 5., -3.076970e+05, 456.32248, 37, 37., 2281612.4),\ (1723161664500000000, 61563.65, 5., -3.076960e+05, 480.9486 , 39, 39., 2404743. ),\ (1723161665500000000, 61555.05, 6., -3.692592e+05, 493.26124, 40, 40., 2466306.2),\ (1723161666500000000, 61530.85, 7., -4.308138e+05, 505.57216, 41, 41., 2527860.8),\ (1723161667500000000, 61522.25, 8., -4.923442e+05, 517.87824, 42, 42., 2589391.2),\ (1723161668500000000, 61543. , 7., -4.308214e+05, 530.1828 , 43, 43., 2650914. ),\ (1723161669500000000, 61528.05, 7., -4.308214e+05, 530.1828 , 43, 43., 2650914. ),\ (1723161670500000000, 61539.85, 8., -4.923490e+05, 542.48832, 44, 44., 2712441.6),\ (1723161671500000000, 61524.15, 9., -5.538884e+05, 554.7962 , 45, 45., 2773981. ),\ (1723161672500000000, 61524.25, 9., -5.538884e+05, 554.7962 , 45, 45., 2773981. ),\ (1723161673500000000, 61535.95, 8., -4.923636e+05, 567.10116, 46, 46., 2835505.8),\ (1723161674500000000, 61531.45, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161675500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161676500000000, 61536.95, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161677500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161678500000000, 61534.75, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161679500000000, 61538.85, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161680500000000, 61538.05, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161681500000000, 61549.25, 9., -5.538990e+05, 579.40824, 47, 47., 2897041.2),\ (1723161682500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161683500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161684500000000, 61552.45, 8., -4.923492e+05, 591.7182 , 48, 48., 2958591. ),\ (1723161685500000000, 61565.95, 7., -4.307963e+05, 604.02878, 49, 49., 3020143.9),\ (1723161686500000000, 61574.45, 6., -3.692299e+05, 616.34206, 50, 50., 3081710.3),\ (1723161687500000000, 61587.55, 6., -3.692299e+05, 616.34206, 50, 50., 3081710.3),\ (1723161688500000000, 61592.95, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161689500000000, 61592.95, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161690500000000, 61594.15, 5., -3.076419e+05, 628.65966, 51, 51., 3143298.3),\ (1723161691500000000, 61598.95, 4., -2.460473e+05, 640.97858, 52, 52., 3204892.9),\ (1723161692500000000, 61593.05, 3., -1.844479e+05, 653.29846, 53, 53., 3266492.3),\ (1723161693500000000, 61582.55, 3., -1.844479e+05, 653.29846, 53, 53., 3266492.3),\ (1723161694500000000, 61582.55, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161695500000000, 61582.55, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161696500000000, 61587.15, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161697500000000, 61587.15, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161698500000000, 61588.75, 4., -2.460299e+05, 665.61486, 54, 54., 3328074.3),\ (1723161699500000000, 61586.75, 4., -2.460289e+05, 690.25034, 56, 56., 3451251.7),\ (1723161700500000000, 61582.05, 5., -3.076151e+05, 702.56758, 57, 57., 3512837.9),\ (1723161701500000000, 61572.05, 6., -3.691967e+05, 714.8839 , 58, 58., 3574419.5),\ (1723161702500000000, 61587.45, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161703500000000, 61577.95, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161704500000000, 61582.05, 5., -3.076241e+05, 727.19842, 59, 59., 3635992.1),\ (1723161705500000000, 61572.05, 5., -3.076189e+05, 751.83042, 61, 61., 3759152.1),\ (1723161706500000000, 61574.05, 4., -2.460463e+05, 764.14494, 62, 62., 3820724.7),\ (1723161707500000000, 61574.05, 4., -2.460463e+05, 764.14494, 62, 62., 3820724.7),\ (1723161708500000000, 61576.05, 3., -1.844717e+05, 776.45986, 63, 63., 3882299.3),\ (1723161709500000000, 61577.55, 2., -1.228951e+05, 788.77518, 64, 64., 3943875.9),\ (1723161710500000000, 61581.95, 1., -6.131710e+04, 801.09078, 65, 65., 4005453.9),\ (1723161711500000000, 61565.65, 1., -6.131710e+04, 801.09078, 65, 65., 4005453.9),\ (1723161712500000000, 61561.15, 2., -1.228823e+05, 813.40382, 66, 66., 4067019.1),\ (1723161713500000000, 61570.45, 2., -1.228813e+05, 838.02826, 68, 68., 4190141.3),\ (1723161714500000000, 61572.45, 1., -6.131040e+04, 850.34244, 69, 69., 4251712.2),\ (1723161715500000000, 61565.65, 1., -6.131040e+04, 850.34244, 69, 69., 4251712.2),\ (1723161716500000000, 61561.95, 2., -1.228756e+05, 862.65548, 70, 70., 4313277.4),\ (1723161717500000000, 61557.05, 3., -1.844370e+05, 874.96776, 71, 71., 4374838.8),\ (1723161718500000000, 61561.95, 3., -1.844370e+05, 874.96776, 71, 71., 4374838.8),\ (1723161719500000000, 61568.05, 2., -1.228746e+05, 887.28024, 72, 72., 4436401.2),\ (1723161720500000000, 61576.55, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161721500000000, 61576.55, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161722500000000, 61589.95, 1., -6.130600e+04, 899.59396, 73, 73., 4497969.8),\ (1723161723500000000, 61593.95, 0., 2.844000e+02, 911.91204, 74, 74., 4559560.2),\ (1723161724500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161725500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161726500000000, 61615.15, -1., 6.187880e+04, 924.23092, 75, 75., 4621154.6),\ (1723161727500000000, 61617.05, -2., 1.234944e+05, 936.55404, 76, 76., 4682770.2),\ (1723161728500000000, 61618.15, -3., 1.851120e+05, 948.87756, 77, 77., 4744387.8),\ (1723161729500000000, 61612.55, -3., 1.851120e+05, 948.87756, 77, 77., 4744387.8),\ (1723161730500000000, 61609.95, -2., 1.235000e+05, 961.19996, 78, 78., 4805999.8),\ (1723161731500000000, 61607.95, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161732500000000, 61608.95, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161733500000000, 61606.05, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161734500000000, 61608.45, -1., 6.189060e+04, 973.52184, 79, 79., 4867609.2),\ (1723161735500000000, 61615.95, -2., 1.234995e+05, 985.84362, 80, 80., 4929218.1),\ (1723161736500000000, 61618.15, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161737500000000, 61605.55, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161738500000000, 61613.85, -3., 1.851159e+05, 998.1669 , 81, 81., 4990834.5),\ (1723161739500000000, 61619.95, -4., 2.467303e+05, 1010.48978, 82, 82., 5052448.9),\ (1723161740500000000, 61636.65, -4., 2.467303e+05, 1010.48978, 82, 82., 5052448.9),\ (1723161741500000000, 61649.75, -5., 3.083675e+05, 1022.81722, 83, 83., 5114086.1),\ (1723161742500000000, 61653.45, -6., 3.700177e+05, 1035.14726, 84, 84., 5175736.3),\ (1723161743500000000, 61668.55, -7., 4.316716e+05, 1047.47804, 85, 85., 5237390.2),\ (1723161744500000000, 61668.55, -7., 4.316716e+05, 1047.47804, 85, 85., 5237390.2),\ (1723161745500000000, 61673.45, -6., 3.700036e+05, 1059.81164, 86, 86., 5299058.2),\ (1723161746500000000, 61675.55, -7., 4.316775e+05, 1072.14642, 87, 87., 5360732.1),\ (1723161747500000000, 61671.35, -8., 4.933535e+05, 1084.48162, 88, 88., 5422408.1),\ (1723161748500000000, 61656.75, -7., 4.316827e+05, 1096.81578, 89, 89., 5484078.9),\ (1723161749500000000, 61660.05, -7., 4.316827e+05, 1096.81578, 89, 89., 5484078.9),\ (1723161750500000000, 61662.05, -8., 4.933433e+05, 1109.1479 , 90, 90., 5545739.5),\ (1723161751500000000, 61652.05, -7., 4.316817e+05, 1121.48022, 91, 91., 5607401.1),\ (1723161752500000000, 61673.45, -7., 4.316817e+05, 1121.48022, 91, 91., 5607401.1),\ (1723161753500000000, 61680.65, -8., 4.933556e+05, 1133.815 , 92, 92., 5669075. ),\ (1723161754500000000, 61672.45, -7., 4.316754e+05, 1146.15104, 93, 93., 5730755.2),\ (1723161755500000000, 61659.95, -6., 3.700035e+05, 1158.48542, 94, 94., 5792427.1),\ (1723161756500000000, 61661.25, -7., 4.316639e+05, 1170.8175 , 95, 95., 5854087.5),\ (1723161757500000000, 61654.25, -7., 4.316639e+05, 1170.8175 , 95, 95., 5854087.5),\ (1723161758500000000, 61650.05, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161759500000000, 61650.05, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161760500000000, 61654.25, -6., 3.700101e+05, 1183.14826, 96, 96., 5915741.3),\ (1723161761500000000, 61652.65, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161762500000000, 61663.95, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161763500000000, 61656.05, -5., 3.083563e+05, 1195.47902, 97, 97., 5977395.1),\ (1723161764500000000, 61656.05, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161765500000000, 61626.35, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161766500000000, 61629.85, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161767500000000, 61629.85, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161768500000000, 61633.05, -6., 3.700129e+05, 1207.81034, 98, 98., 6039051.7),\ (1723161769500000000, 61645.45, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161770500000000, 61645.45, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161771500000000, 61649.75, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161772500000000, 61640.85, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161773500000000, 61642.05, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161774500000000, 61629.85, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161775500000000, 61629.75, -7., 4.316465e+05, 1220.13706, 99, 99., 6100685.3),\ (1723161776500000000, 61635.25, -8., 4.932767e+05, 1232.4631 , 100, 100., 6162315.5),\ (1723161777500000000, 61618.75, -7., 4.316419e+05, 1244.79006, 101, 101., 6223950.3),\ (1723161778500000000, 61615.75, -6., 3.700237e+05, 1257.1137 , 102, 102., 6285568.5),\ (1723161779500000000, 61602.7 , -5., 3.084085e+05, 1269.43674, 103, 103., 6347183.7),\ (1723161780500000000, 61609.75, -4., 2.468063e+05, 1281.75718, 104, 104., 6408785.9),\ (1723161781500000000, 61629.65, -5., 3.084165e+05, 1294.07922, 105, 105., 6470396.1),\ (1723161782500000000, 61634.15, -6., 3.700467e+05, 1306.40526, 106, 106., 6532026.3),\ (1723161783500000000, 61631.95, -5., 3.084131e+05, 1318.73198, 107, 107., 6593659.9),\ (1723161784500000000, 61625.55, -4., 2.467817e+05, 1331.05826, 108, 108., 6655291.3),\ (1723161785500000000, 61617.05, -3., 1.851567e+05, 1343.38326, 109, 109., 6716916.3),\ (1723161786500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161787500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161788500000000, 61620.05, -2., 1.235401e+05, 1355.70658, 110, 110., 6778532.9),\ (1723161789500000000, 61617.05, -1., 6.192050e+04, 1368.0305 , 111, 111., 6840152.5),\ (1723161790500000000, 61616.15, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161791500000000, 61616.15, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161792500000000, 61618.05, 0., 3.039000e+02, 1380.35382, 112, 112., 6901769.1),\ (1723161793500000000, 61642.05, -1., 6.192250e+04, 1392.67754, 113, 113., 6963387.7),\ (1723161794500000000, 61649.95, -2., 1.235651e+05, 1405.00606, 114, 114., 7025030.3),\ (1723161795500000000, 61666.45, -3., 1.852155e+05, 1417.33614, 115, 115., 7086680.7),\ (1723161796500000000, 61658.05, -3., 1.852155e+05, 1417.33614, 115, 115., 7086680.7),\ (1723161797500000000, 61648.45, -2., 1.235579e+05, 1429.66766, 116, 116., 7148338.3),\ (1723161798500000000, 61642.05, -2., 1.235589e+05, 1454.32702, 118, 118., 7271635.1),\ (1723161799500000000, 61638.85, -1., 6.191730e+04, 1466.65534, 119, 119., 7333276.7),\ (1723161800500000000, 61631.95, 0., 2.789000e+02, 1478.98302, 120, 120., 7394915.1),\ (1723161801500000000, 61623.05, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161802500000000, 61626.65, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161803500000000, 61625.05, 1., -6.135250e+04, 1491.3093 , 121, 121., 7456546.5),\ (1723161804500000000, 61612.05, 2., -1.229771e+05, 1503.63422, 122, 122., 7518171.1),\ (1723161805500000000, 61618.05, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161806500000000, 61616.15, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161807500000000, 61616.15, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161808500000000, 61602.45, 1., -6.136450e+04, 1515.95674, 123, 123., 7579783.7),\ (1723161809500000000, 61592.05, 2., -1.229664e+05, 1528.27712, 124, 124., 7641385.6),\ (1723161810500000000, 61580.25, 3., -1.845580e+05, 1540.59544, 125, 125., 7702977.2),\ (1723161811500000000, 61580.25, 3., -1.845580e+05, 1540.59544, 125, 125., 7702977.2),\ (1723161812500000000, 61586.25, 2., -1.229772e+05, 1552.9116 , 126, 126., 7764558. ),\ (1723161813500000000, 61594.45, 1., -6.139040e+04, 1565.22896, 127, 127., 7826144.8),\ (1723161814500000000, 61606.85, 0., 2.045000e+02, 1577.54794, 128, 128., 7887739.7),\ (1723161815500000000, 61630.35, -1., 6.181190e+04, 1589.86942, 129, 129., 7949347.1),\ (1723161816500000000, 61638.05, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161817500000000, 61626.25, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161818500000000, 61626.25, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161819500000000, 61613.65, -2., 1.234427e+05, 1602.19558, 130, 130., 8010977.9),\ (1723161820500000000, 61608.15, -1., 6.182950e+04, 1614.51822, 131, 131., 8072591.1),\ (1723161821500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161822500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161823500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161824500000000, 61624.65, -2., 1.234381e+05, 1626.83994, 132, 132., 8134199.7),\ (1723161825500000000, 61622.55, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161826500000000, 61622.55, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161827500000000, 61621.65, -1., 6.181390e+04, 1639.16478, 133, 133., 8195823.9),\ (1723161828500000000, 61615.95, 0., 1.927000e+02, 1651.48902, 134, 134., 8257445.1),\ (1723161829500000000, 61621.55, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161830500000000, 61621.55, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161831500000000, 61614.05, 1., -6.142270e+04, 1663.8121 , 135, 135., 8319060.5),\ (1723161832500000000, 61611.55, 2., -1.230363e+05, 1676.13482, 136, 136., 8380674.1),\ (1723161833500000000, 61620.05, 2., -1.230363e+05, 1676.13482, 136, 136., 8380674.1),\ (1723161834500000000, 61622.55, 1., -6.141570e+04, 1688.45894, 137, 137., 8442294.7),\ (1723161835500000000, 61621.55, 1., -6.141470e+04, 1713.10794, 139, 139., 8565539.7),\ (1723161836500000000, 61630.35, 0., 2.073000e+02, 1725.43234, 140, 140., 8627161.7),\ (1723161837500000000, 61613.75, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161838500000000, 61613.75, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161839500000000, 61605.05, -1., 6.183810e+04, 1737.7585 , 141, 141., 8688792.5),\ (1723161840500000000, 61616.05, -2., 1.234437e+05, 1750.07962, 142, 142., 8750398.1),\ (1723161841500000000, 61621.55, -3., 1.850603e+05, 1762.40294, 143, 143., 8812014.7),\ (1723161842500000000, 61633.95, -4., 2.466823e+05, 1774.72734, 144, 144., 8873636.7),\ (1723161843500000000, 61638.05, -5., 3.083167e+05, 1787.05422, 145, 145., 8935271.1),\ (1723161844500000000, 61634.95, -4., 2.466791e+05, 1799.38174, 146, 146., 8996908.7),\ (1723161845500000000, 61634.95, -4., 2.466791e+05, 1799.38174, 146, 146., 8996908.7),\ (1723161846500000000, 61638.05, -5., 3.083145e+05, 1811.70882, 147, 147., 9058544.1),\ (1723161847500000000, 61634.95, -5., 3.083155e+05, 1836.36406, 149, 149., 9181820.3),\ (1723161848500000000, 61626.05, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161849500000000, 61629.95, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161850500000000, 61629.95, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161851500000000, 61632.25, -4., 2.466811e+05, 1848.69094, 150, 150., 9243454.7),\ (1723161852500000000, 61635.95, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161853500000000, 61635.95, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161854500000000, 61638.05, -5., 3.083139e+05, 1861.0175 , 151, 151., 9305087.5),\ (1723161855500000000, 61636.25, -4., 2.466763e+05, 1873.34502, 152, 152., 9366725.1),\ (1723161856500000000, 61638.05, -4., 2.466763e+05, 1873.34502, 152, 152., 9366725.1),\ (1723161857500000000, 61636.05, -5., 3.083149e+05, 1885.67274, 153, 153., 9428363.7),\ (1723161858500000000, 61641.45, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161859500000000, 61641.45, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161860500000000, 61643.25, -6., 3.699515e+05, 1898.00006, 154, 154., 9490000.3),\ (1723161861500000000, 61657.25, -7., 4.315953e+05, 1910.32882, 155, 155., 9551644.1),\ (1723161862500000000, 61671.45, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161863500000000, 61668.05, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161864500000000, 61669.15, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161865500000000, 61666.75, -8., 4.932531e+05, 1922.66038, 156, 156., 9613301.9),\ (1723161866500000000, 61665.05, -7., 4.315869e+05, 1934.99362, 157, 157., 9674968.1),\ (1723161867500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161868500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161869500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161870500000000, 61657.15, -6., 3.699223e+05, 1947.32654, 158, 158., 9736632.7),\ (1723161871500000000, 61666.75, -7., 4.315799e+05, 1959.65806, 159, 159., 9798290.3),\ (1723161872500000000, 61651.55, -6., 3.699137e+05, 1971.9913 , 160, 160., 9859956.5),\ (1723161873500000000, 61638.05, -5., 3.082627e+05, 1984.3215 , 161, 161., 9921607.5),\ (1723161874500000000, 61634.35, -4., 2.466251e+05, 1996.64902, 162, 162., 9983245.1),\ (1723161875500000000, 61638.85, -4., 2.466251e+05, 1996.64902, 162, 162., 9983245.1),\ (1723161876500000000, 61638.15, -5., 3.082645e+05, 2008.9769 , 163, 163., 10044884.5),\ (1723161877500000000, 61621.65, -4., 2.466269e+05, 2021.30442, 164, 164., 10106522.1),\ (1723161878500000000, 61611.65, -3., 1.850057e+05, 2033.62866, 165, 165., 10168143.3),\ (1723161879500000000, 61614.95, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161880500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161881500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161882500000000, 61614.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161883500000000, 61616.15, -4., 2.466179e+05, 2045.9511 , 166, 166., 10229755.5),\ (1723161884500000000, 61623.95, -5., 3.082345e+05, 2058.27442, 167, 167., 10291372.1),\ (1723161885500000000, 61627.95, -6., 3.698589e+05, 2070.5993 , 168, 168., 10352996.5),\ (1723161886500000000, 61621.45, -6., 3.698589e+05, 2070.5993 , 168, 168., 10352996.5),\ (1723161887500000000, 61620.45, -5., 3.082380e+05, 2082.92348, 169, 169., 10414617.4),\ (1723161888500000000, 61617.55, -4., 2.466181e+05, 2095.24746, 170, 170., 10476237.3),\ (1723161889500000000, 61609.45, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161890500000000, 61609.45, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161891500000000, 61605.95, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161892500000000, 61605.95, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161893500000000, 61596.55, -3., 1.850011e+05, 2107.57086, 171, 171., 10537854.3),\ (1723161894500000000, 61595.65, -2., 1.234051e+05, 2119.89006, 172, 172., 10599450.3),\ (1723161895500000000, 61580.75, -1., 6.180990e+04, 2132.2091 , 173, 173., 10661045.5),\ (1723161896500000000, 61575.05, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161897500000000, 61585.05, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161898500000000, 61578.25, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161899500000000, 61578.25, 0., 2.297000e+02, 2144.52514, 174, 174., 10722625.7),\ (1723161900500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161901500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161902500000000, 61583.95, -1., 6.180850e+04, 2156.8409 , 175, 175., 10784204.5),\ (1723161903500000000, 61585.05, -2., 1.233929e+05, 2169.15778, 176, 176., 10845788.9)\], dtype={'names': \['timestamp', 'price', 'position', 'balance', 'fee', 'num\_trades', 'trading\_volume', 'trading\_value'\], 'formats': \[') ![]() \[33\]: --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Making Multiple Markets - Introduction * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb.txt) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Link to this heading") ======================================================================================================================================================================================================================= One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * JIT Compilation Overhead * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/jit_compilation_overhead.rst) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/v1.8.4/jit_compilation_overhead.html#jit-compilation-overhead "Permalink to this heading") ============================================================================================================================================================= HftBacktest takes advantage of Numba’s capabilities, with a significant portion of its implementation relying on Numba JIT’ed classes. As a result, the first run of HftBacktest requires JIT compilation, which can take several tens of seconds. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature along with `reset` method to reset HftBacktest. See the example below. from numba import njit from hftbacktest import HftBacktest, IntpOrderLatency, SquareProbQueueModel, Linear \# enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/ethusdt\_20221002\_eod.npz' ) algo(arguments, hbt) When you need to execute the same code using varying arguments or different datasets, you can proceed as follows. from hftbacktest import reset reset( hbt, \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], snapshot\='data/ethusdt\_20221002\_eod.npz' ) algo(arguments, hbt) --- # Examples — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Examples * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/examples.rst) * * * Examples[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/examples.html#examples "Permalink to this heading") ======================================================================================================================= You can find more examples [here](https://github.com/nkaz001/hftbacktest/tree/master/examples) --- # Asset Types — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Asset Types * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/reference/asset_types.rst) * * * Asset Types[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/asset_types.html#asset-types "Permalink to this heading") ================================================================================================================================ _class_ LinearAsset(_contract\_size\=1_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/assettype.html#LinearAsset) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/asset_types.html#hftbacktest.assettype.LinearAsset "Permalink to this definition") Linear asset: the common type of asset. Parameters: **contract\_size** (_int64_) – Contract size of the asset. _class_ InverseAsset(_contract\_size\=1_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/assettype.html#InverseAsset) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/asset_types.html#hftbacktest.assettype.InverseAsset "Permalink to this definition") Inverse asset: the contract’s notional value is denominated in the quote currency. Parameters: **contract\_size** (_int64_) – Contract size of the asset. --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Debugging Backtesting and Live Discrepancies * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/debugging_backtesting_and_live_discrepancies.rst) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/v1.8.4/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Permalink to this heading") ========================================================================================================================================================================================================================= Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as PowerProbQueueModel series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/latest/order_fill.html#probqueuemodel) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Making Multiple Markets - Introduction * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Permalink to this heading") ========================================================================================================================================================================================================================= One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ============================================================================================================================================================================================================================================================================================================================ For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Making Multiple Markets - Introduction * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb.txt) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Link to this heading") ======================================================================================================================================================================================================================= One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Order Latency Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Order Latency Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Order%20Latency%20Data.ipynb.txt) * * * Order Latency Data[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Order%20Latency%20Data.html#Order-Latency-Data "Link to this heading") ======================================================================================================================================================= To obtain more realistic backtesting results, accounting for latencies is crucial. Therefore, it’s important to collect both feed data and order data with timestamps to measure your order latency. The best approach is to gather your own order latencies. You can collect order latency based on your live trading or by regularly submitting orders at a price that cannot be filled and then canceling them for recording purposes. However, if you don’t have access to them or want to establish a target, you will need to artificially generate order latency. You can model this latency based on factors such as feed latency, trade volume, and the number of events. In this guide, we will demonstrate a simple method to generate order latency from feed latency using a multiplier and offset for adjustment. First, loads the feed data. \[1\]: import numpy as np data \= np.load('btcusdt\_20200201.npz')\['data'\] data \[1\]: array(\[(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\ ...,\ (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\ (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\ (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)\], dtype=\[('ev', ' \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True Bybit[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading%20-%20Comparison%20Across%20Other%20Exchanges.html#Bybit "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.0025% rebate, the market maker rebate available on Bybit Futures. See Introduction to the Market Maker Incentive Program for more details. \[3\]: dates \= \[\] date \= datetime.datetime(2025, 4, 1) until \= datetime.datetime(2025, 5, 19) while date <= until: dates.append(date.strftime("%Y%m%d")) date += datetime.timedelta(days\=1) latency\_data \= np.concatenate( \[np.load('bybit\_latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in dates\] ) def backtest(args): asset\_name, asset\_info, half\_spread \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 data \= \['bybit\_data/{}/{}\_{}.npz'.format(asset\_name, asset\_name, date) for date in dates\] asset \= ( BacktestAsset() .data(data) \# Tardis collects Bybit data from Tokyo, but the Bybit server is located in Singapore. # \# Therefore, if we assume our strategy will run in Singapore, we need to adjust for the feed latency. \# The round-trip time (RTT) between Tokyo and Singapore is approximately 70 ms. \# For our purposes, we subtract 30 ms as the estimated one-way latency from Singapore to Tokyo, including a small buffer. # \# https://docs.tardis.dev/historical-data-details/bybit#market-data-collection-details \# https://bybit-exchange.github.io/docs/faq#where-are-bybits-servers-located \# https://elitwilliams.medium.com/geographic-latency-in-crypto-how-to-optimally-co-locate-your-aws-trading-server-to-any-exchange-58965ea173a8 .latency\_offset(\-30\_000\_000) .initial\_snapshot('bybit\_data/{}/{}\_20250331\_eod.npz'.format(asset\_name, asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.000025, 0.00055) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] grid\_num \= 20 grid\_interval \= half\_spread skew \= half\_spread / grid\_num min\_grid\_step \= asset\_info\['tick\_size'\] recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, min\_grid\_step, grid\_num, skew, order\_qty) hbt.close() recorder.to\_npz('bybit\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread)) \[4\]: %%capture with open('bybit\_assets.json', 'r') as f: assets \= json.load(f) args \= list(itertools.product(list(assets.items()), \[0.0005, 0.0010, 0.0015\])) args \= \[(\*tup, x) for tup, x in args\] with Pool(16) as p: print(p.map(backtest, args)) As we have demonstrated so far, the strategy collectively produces a favorable equity curve when factoring in rebates. \[6\]: equity\_values \= {} half\_spread \= 0.001 for asset\_name, \_ in assets.items(): data \= np.load('bybit\_stats/gridtrading\_{}\_{}.npz'.format(asset\_name, half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[7\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\]#\[:-1\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[7\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_7_1.png) In addition, this is for demonstration purpose to use the single parameter set, but you can find more optimum parameter set for each pair, which also have a risk to lead to the overfitting. \[9\]: for half\_spread in \[0.0005, 0.001, 0.0015\]: data \= np.load('bybit\_stats/gridtrading\_SUIUSDT\_{}.npz'.format(name, half\_spread))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) stats.plot() SUIUSDT 0.0005 ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_1.png) SUIUSDT 0.001 ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_3.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_3.png) SUIUSDT 0.0015 ![../_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_5.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_-_Comparison_Across_Other_Exchanges_9_5.png) Regarding applying the same parameter set to the multiple pairs in generalized way, you need more generalized model about the volatility and the order flow such like you’ve seen in the GLFT example. Also, you can see volatility regime change over time-horizon affecting the performance in April in the plot. We will provide the example another emprical approach other than the GLFT example, as a simplified version using non-parametric approach. --- # Migration to v2 — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Migration to v2 * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/migration2.rst.txt) * * * Migration to v2[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/migration2.html#migration-to-v2 "Link to this heading") =========================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/migration2.html#overview "Link to this heading") ------------------------------------------------------------------------------------------------------------- The migration from version 1 to version 2 introduces several significant changes that can cause errors if the same code is used without modification. It is highly recommended to review the updated tutorials. This guide aims to help you avoid common pitfalls during the migration process. Checking Success: Use `elapse() == 0`[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/migration2.html#checking-success-use-elapse-0 "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------- In version 1, `elapse` function returns `True` on success and `False` otherwise. Typically, the strategy loop checks for successful elapsing using `while elapse(duration)`. However, in version 2, elapse returns a code instead of a boolean, with `0` indicating success and any other value indicating an error. Consequently, the code should be updated to check if the return value equals `0`. For instance: `while elapse(duration) == 0` If the code remains unchanged, it will fail because a return value of `0` (indicating success) will be treated as `False`. Other methods that involve elapsing, such as `submit_buy_order` or `submit_sell_order`, also return a code similar to `elapse` instead of a boolean. Ensure to check if their return values equal `0` to confirm success instead of checking for `True`. Data Format Changes[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/migration2.html#data-format-changes "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- The data format fed into HftBacktest has undergone significant changes. It is strongly recommended to reprocess the data from raw data to preserve all information. However, if raw data is unavailable, [`the data conversion utility`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2 "hftbacktest.data.utils.migration2") from v1 to v2 is provided. The major changes are as follows: * SOA to AOS: The format has shifted from a columnar array (SOA) to a structured array (AOS). * Side Column Removal: `side` column has been removed. In version 2, the side is indicated by the `ev` field flags, [`BUY_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.BUY_EVENT "hftbacktest.types.BUY_EVENT") and [`SELL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.SELL_EVENT "hftbacktest.types.SELL_EVENT") . * Timestamp Handling: In version 1, the data utility corrects the event order by replacing one of the timestamps with `-1` to indicate an invalid event on either the exchange or the local side. In version 2, the validity of events on the exchange or local side is determined by ev field’s [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. * Timestamp Unit: Although not strictly enforced, the timestamp unit has changed from microseconds to nanoseconds. Additionally, the format for live order latency data has changed from SOA to AOS. --- # Latency Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Latency Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/latency_models.rst.txt) * * * Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#latency-models "Link to this heading") ============================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------- Latency is an important factor that you need to take into account when you backtest your HFT strategy. HftBacktest has three types of latencies. ![_images/latencies.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/latencies.png) * Feed latency This is the latency between the time the exchange sends the feed events such as order book change or trade and the time it is received by the local. This latency is dealt with through two different timestamps: local timestamp and exchange timestamp. * Order entry latency This is the latency between the time you send an order request and the time it is processed by the exchange’s matching engine. * Order response latency This is the latency between the time the exchange’s matching engine processes an order request and the time the order response is received by the local. The response to your order fill is also affected by this type of latency. ![_images/latency-comparison.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/latency-comparison.png) Order Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#order-latency-models "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- HftBacktest provides the following order latency models and you can also implement your own latency model. ### ConstantLatency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#constantlatency "Link to this heading") It’s the most basic model that uses constant latencies. You just set the latencies. You can find details below. * [ConstantLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ConstantLatency.html) and [`constant_latency`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency "hftbacktest.BacktestAsset.constant_latency") ### IntpOrderLatency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#intporderlatency "Link to this heading") This model interpolates order latency based on the actual order latency data. This is the most accurate among the provided models if you have the data with a fine time interval. You can collect the latency data by submitting unexecutable orders regularly. You can find details below. * [IntpOrderLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html) and [`intp_order_latency`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency "hftbacktest.BacktestAsset.intp_order_latency") **Data example** req\_ts (request timestamp at local), exch\_ts (exchange timestamp), resp\_ts (receipt timestamp at local), \_padding 1670026844751525000, 1670026844759000000, 1670026844762122000, 0 1670026845754020000, 1670026845762000000, 1670026845770003000, 0 ### FeedLatency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#feedlatency "Link to this heading") If the live order latency data is unavailable, you can generate artificial order latency using feed latency. Please refer to [this tutorial](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Order%20Latency%20Data.html) for guidance. ### Implement your own order latency model[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/latency_models.html#implement-your-own-order-latency-model "Link to this heading") You need to implement the following trait. * [LatencyModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.LatencyModel.html) Please refer to [the latency model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/latency.rs) . --- # Integrating Custom Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Integrating Custom Data * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Integrating%20Custom%20Data.ipynb) * * * Integrating Custom Data[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Integrating%20Custom%20Data.html#Integrating-Custom-Data "Permalink to this heading") ======================================================================================================================================================================== By combining your custom data with the feed data (order book and trades), you can enhance your strategy while harnessing the full potential of hftbacktest. Accessing Spot Price[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Integrating%20Custom%20Data.html#Accessing-Spot-Price "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------ In this example, we’ll combine the spot BTCUSDT mid-price with the USDM-Futures BTCUSDT feed data. This will enable you to estimate the fair value price, taking the underlying price into consideration. The spot data is used only in the local-side, and thus, should come with a local timestamp. Following this, in your backtesting logic, your task is to identify the most recent data that predates the current timestamp. The raw spot feed is processed to create spot data, which includes both a local timestamp and the spot mid price. \[1\]: import numpy as np import gzip import json spot \= np.full((100\_000, 2), np.nan, np.float64) i \= 0 with gzip.open('spot/btcusdt\_20230405.dat.gz', 'r') as f: while True: line \= f.readline() if line is None or line \== b'': break line \= line.decode().strip() local\_timestamp \= int(line\[:16\]) obj \= json.loads(line\[17:\]) if obj\['stream'\] \== 'btcusdt@bookTicker': data \= obj\['data'\] mid \= (float(data\['b'\]) + float(data\['a'\])) / 2.0 \# Sets the event ID to 110 and assign an invalid exchange timestamp, \# as it's not utilized in the exchange simulation. \# And stores the mid-price in the price column. spot\[i\] \= \[local\_timestamp, mid\] i += 1 spot \= spot\[:i\] It displays the basis and spot mid price as it identifies the latest Point-in-Time data that falls before the current timestamp. \[2\]: from numba import njit from hftbacktest import HftBacktest, FeedLatency, Linear @njit def print\_basis(hbt, spot): spot\_row \= 0 \# Checks every 60-sec (in microseconds) while hbt.elapse(60\_000\_000): \# Finds the latest spot mid value. while spot\_row < len(spot) and spot\[spot\_row, 0\] <= hbt.current\_timestamp: spot\_row += 1 spot\_mid\_price \= spot\[spot\_row \- 1, 1\] if spot\_row \> 0 else np.nan mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 basis \= mid\_price \- spot\_mid\_price print( 'current\_timestamp:', hbt.current\_timestamp, 'futures\_mid:', round(mid\_price, 2), ', spot\_mid:', round(spot\_mid\_price, 2), ', basis:', round(basis, 2) ) hbt \= HftBacktest( \[\ 'btcusdt\_20230405\_m.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\='btcusdt\_20230404\_eod.npz' ) print\_basis(hbt, spot) Load btcusdt\_20230405\_m.npz current\_timestamp: 1680652860032116 futures\_mid: 28150.75 , spot\_mid: 28164.42 , basis: -13.67 current\_timestamp: 1680652920032116 futures\_mid: 28144.15 , spot\_mid: 28155.82 , basis: -11.67 current\_timestamp: 1680652980032116 futures\_mid: 28149.95 , spot\_mid: 28163.48 , basis: -13.53 current\_timestamp: 1680653040032116 futures\_mid: 28145.75 , spot\_mid: 28158.88 , basis: -13.12 current\_timestamp: 1680653100032116 futures\_mid: 28140.55 , spot\_mid: 28156.06 , basis: -15.51 current\_timestamp: 1680653160032116 futures\_mid: 28143.85 , spot\_mid: 28155.82 , basis: -11.97 Combining Spot Price[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Integrating%20Custom%20Data.html#Combining-Spot-Price "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------ While integrating custom data with feed data might be more challenging than simply accessing the data demonstrated in the first example, this process could be necessary if you’re intending to develop your own custom exchange model. Viewing the custom data from the exchange-side could indeed provide a more comprehensive approach to backtesting, such as when considering funding. \[3\]: tmp \= np.full((100\_000, 6), np.nan, np.float64) i \= 0 with gzip.open('spot/btcusdt\_20230405.dat.gz', 'r') as f: while True: line \= f.readline() if line is None or line \== b'': break line \= line.decode().strip() local\_timestamp \= int(line\[:16\]) obj \= json.loads(line\[17:\]) if obj\['stream'\] \== 'btcusdt@bookTicker': data \= obj\['data'\] mid \= (float(data\['b'\]) + float(data\['a'\])) / 2.0 \# Sets the event ID to 110 and assign an invalid exchange timestamp, \# as it's not utilized in the exchange simulation. \# And stores the mid-price in the price column. tmp\[i\] \= \[110, \-1, local\_timestamp, 0, mid, 0\] i += 1 tmp \= tmp\[:i\] You can merge the two data sets using `merge_on_local_timestamp` and then proceed to validate the data. \[4\]: from hftbacktest import merge\_on\_local\_timestamp, validate\_data usdm\_feed\_data \= np.load('btcusdt\_20230405\_m.npz')\['data'\] merged \= merge\_on\_local\_timestamp(usdm\_feed\_data, tmp) validate\_data(merged) \[4\]: 0 You can obtain the spot mid-price by using `get_user_data` function along with event id 110. \[5\]: from hftbacktest import reset, COL\_PRICE @njit def print\_basis(hbt): \# Checks every 60-sec (in microseconds) while hbt.elapse(60\_000\_000): funding\_rate \= hbt.get\_user\_data(102) spot\_mid\_price \= hbt.get\_user\_data(110) mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 basis \= mid\_price \- spot\_mid\_price\[COL\_PRICE\] print( 'current\_timestamp:', hbt.current\_timestamp, 'futures\_mid:', round(mid\_price, 2), 'funding\_rate:', funding\_rate\[COL\_PRICE\], ', spot\_mid:', round(spot\_mid\_price\[COL\_PRICE\], 2), ', basis:', round(basis, 2) ) reset( hbt, \[\ merged\ \], snapshot\='btcusdt\_20230404\_eod.npz' ) print\_basis(hbt) current\_timestamp: 1680652860004231 futures\_mid: 28150.75 funding\_rate: 2.76e-05 , spot\_mid: 28164.42 , basis: -13.67 current\_timestamp: 1680652920004231 futures\_mid: 28144.15 funding\_rate: 2.813e-05 , spot\_mid: 28155.82 , basis: -11.67 current\_timestamp: 1680652980004231 futures\_mid: 28149.95 funding\_rate: 2.826e-05 , spot\_mid: 28163.48 , basis: -13.53 current\_timestamp: 1680653040004231 futures\_mid: 28145.75 funding\_rate: 2.826e-05 , spot\_mid: 28158.88 , basis: -13.12 current\_timestamp: 1680653100004231 futures\_mid: 28140.55 funding\_rate: 2.841e-05 , spot\_mid: 28156.06 , basis: -15.51 current\_timestamp: 1680653160004231 futures\_mid: 28143.85 funding\_rate: 2.85e-05 , spot\_mid: 28155.82 , basis: -11.97 Combining Funding Rate by Using Built-in Data Utility[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Integrating%20Custom%20Data.html#Combining-Funding-Rate-by-Using-Built-in-Data-Utility "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ If you’re using data that has been converted from raw feed by the built-in utility, you can effortlessly incorporate `markPrice` stream data. Find out more details [here](https://hftbacktest.readthedocs.io/en/latest/reference/hftbacktest.data.utils.binancefutures.html) . \[6\]: from hftbacktest.data.utils import binancefutures data \= binancefutures.convert('usdm/btcusdt\_20230405.dat.gz', opt\='m') np.savez('btcusdt\_20230405\_m', data\=data) local\_timestamp is ahead of exch\_timestamp by 26932.0 found 6555 rows that exch\_timestamp is ahead of the previous exch\_timestamp Correction is done. You can obtain the funding rate by using `get_user_data` function along with event id 102. \[7\]: @njit def print\_funding\_rate(hbt): \# Checks every 60-sec (in microseconds) while hbt.elapse(60\_000\_000): \# funding\_rate data is stored with event id 102. funding\_rate \= hbt.get\_user\_data(102) mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 print( 'current\_timestamp:', hbt.current\_timestamp, 'futures\_mid:', round(mid\_price, 2), 'funding\_rate:', funding\_rate\[COL\_PRICE\] ) reset( hbt, \[\ 'btcusdt\_20230405\_m.npz'\ \], snapshot\='btcusdt\_20230404\_eod.npz' ) print\_funding\_rate(hbt) Load btcusdt\_20230405\_m.npz current\_timestamp: 1680652860032116 futures\_mid: 28150.75 funding\_rate: 2.76e-05 current\_timestamp: 1680652920032116 futures\_mid: 28144.15 funding\_rate: 2.813e-05 current\_timestamp: 1680652980032116 futures\_mid: 28149.95 funding\_rate: 2.826e-05 current\_timestamp: 1680653040032116 futures\_mid: 28145.75 funding\_rate: 2.826e-05 current\_timestamp: 1680653100032116 futures\_mid: 28140.55 funding\_rate: 2.841e-05 current\_timestamp: 1680653160032116 futures\_mid: 28143.85 funding\_rate: 2.85e-05 --- # Working with Market Depth and Trades — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Working with Market Depth and Trades * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Working%20with%20Market%20Depth%20and%20Trades.ipynb) * * * Working with Market Depth and Trades[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Working-with-Market-Depth-and-Trades "Permalink to this heading") ===================================================================================================================================================================================================================== Display 3-depth[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-3-depth "Permalink to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: from numba import njit @njit def print\_3depth(hbt): while hbt.elapse(60 \* 1e6): \# a key of bid\_depth or ask\_depth is price in tick format. \# (integer) price\_tick = price / tick\_size print('current\_timestamp:', hbt.current\_timestamp) i \= 0 for tick\_price in range(hbt.best\_ask\_tick, hbt.high\_ask\_tick + 1): if tick\_price in hbt.ask\_depth: print( 'ask: ', hbt.ask\_depth\[tick\_price\], '@', round(tick\_price \* hbt.tick\_size, 3) ) i += 1 if i \== 3: break i \= 0 for tick\_price in range(hbt.best\_bid\_tick, hbt.low\_bid\_tick \- 1, \-1): if tick\_price in hbt.bid\_depth: print( 'bid: ', hbt.bid\_depth\[tick\_price\], '@', round(tick\_price \* hbt.tick\_size, 3) ) i += 1 if i \== 3: break return True \[2\]: import numpy as np btcusdt\_20230405 \= np.load('btcusdt\_20230405.npz')\['data'\] btcusdt\_20230404\_eod \= np.load('btcusdt\_20230404\_eod.npz')\['data'\] \[3\]: from hftbacktest import HftBacktest, FeedLatency, Linear hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) print\_3depth(hbt) current\_timestamp: 1680652860032116 ask: 9.228 @ 28150.8 ask: 0.387 @ 28150.9 ask: 3.996 @ 28151.0 bid: 3.135 @ 28150.7 bid: 0.002 @ 28150.6 bid: 0.813 @ 28150.5 current\_timestamp: 1680652920032116 ask: 1.224 @ 28144.2 ask: 0.223 @ 28144.3 ask: 0.001 @ 28144.5 bid: 10.529 @ 28144.1 bid: 0.168 @ 28144.0 bid: 0.29 @ 28143.9 current\_timestamp: 1680652980032116 ask: 3.397 @ 28150.0 ask: 1.282 @ 28150.1 ask: 0.003 @ 28150.4 bid: 7.951 @ 28149.9 bid: 0.02 @ 28149.8 bid: 0.02 @ 28149.7 current\_timestamp: 1680653040032116 ask: 3.905 @ 28145.8 ask: 1.695 @ 28145.9 ask: 0.003 @ 28146.0 bid: 5.793 @ 28145.7 bid: 0.059 @ 28145.6 bid: 0.044 @ 28145.5 current\_timestamp: 1680653100032116 ask: 6.8 @ 28140.6 ask: 0.001 @ 28140.7 ask: 0.004 @ 28141.1 bid: 2.416 @ 28140.5 bid: 0.004 @ 28140.4 bid: 0.012 @ 28140.3 current\_timestamp: 1680653160032116 ask: 3.666 @ 28143.9 ask: 1.422 @ 28144.0 ask: 1.455 @ 28144.1 bid: 3.189 @ 28143.8 bid: 5.136 @ 28143.7 bid: 0.012 @ 28143.5 \[3\]: True Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Order-Book-Imbalance "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: @njit def orderbookimbalance(hbt, out): while hbt.elapse(10 \* 1e6): mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 sum\_ask\_qty\_50bp \= 0.0 sum\_ask\_qty \= 0.0 for tick\_price in range(hbt.best\_ask\_tick, hbt.high\_ask\_tick + 1): if tick\_price in hbt.ask\_depth: ask\_price \= tick\_price \* hbt.tick\_size depth\_from\_mid \= (ask\_price \- mid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_ask\_qty += hbt.ask\_depth\[tick\_price\] if depth\_from\_mid <= 0.005: sum\_ask\_qty\_50bp \= sum\_ask\_qty sum\_bid\_qty\_50bp \= 0.0 sum\_bid\_qty \= 0.0 for tick\_price in range(hbt.best\_bid\_tick, hbt.low\_bid\_tick \- 1, \-1): if tick\_price in hbt.bid\_depth: bid\_price \= tick\_price \* hbt.tick\_size depth\_from\_mid \= (mid\_price \- bid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_bid\_qty += hbt.bid\_depth\[tick\_price\] if depth\_from\_mid <= 0.005: sum\_bid\_qty\_50bp \= sum\_bid\_qty imbalance\_50bp \= sum\_bid\_qty\_50bp \- sum\_ask\_qty\_50bp imbalance\_1pct \= sum\_bid\_qty \- sum\_ask\_qty imbalance\_tob \= hbt.bid\_depth\[hbt.best\_bid\_tick\] \- hbt.ask\_depth\[hbt.best\_ask\_tick\] out.append((hbt.current\_timestamp, imbalance\_tob, imbalance\_50bp, imbalance\_1pct)) return True \[5\]: from numba.typed import List from numba.types import Tuple, float64 hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) tup\_ty \= Tuple((float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) orderbookimbalance(hbt, out) \[5\]: True \[6\]: import polars as pl df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', 'TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='us') ) df \[6\]: shape: (41, 4) | Local Timestamp | TOB Imbalance | 0.5% Imbalance | 1% Imbalance | | --- | --- | --- | --- | | datetime\[μs\] | f64 | f64 | f64 | | --- | --- | --- | --- | | 2023-04-05 00:00:10.032116 | 10.62 | 79.522 | \-521.026 | | 2023-04-05 00:00:20.032116 | \-4.868 | \-152.592 | \-684.247 | | 2023-04-05 00:00:30.032116 | 0.59 | \-161.95 | \-701.843 | | 2023-04-05 00:00:40.032116 | 3.962 | \-142.51 | \-669.033 | | 2023-04-05 00:00:50.032116 | 4.912 | \-114.827 | \-653.331 | | 2023-04-05 00:01:00.032116 | \-6.093 | 273.067 | \-755.551 | | 2023-04-05 00:01:10.032116 | 10.201 | \-36.007 | \-701.998 | | 2023-04-05 00:01:20.032116 | 3.009 | 121.205 | \-735.241 | | 2023-04-05 00:01:30.032116 | 8.383 | 381.521 | \-704.022 | | 2023-04-05 00:01:40.032116 | 8.672 | 166.934 | \-691.313 | | 2023-04-05 00:01:50.032116 | \-2.575 | 393.212 | \-695.99 | | 2023-04-05 00:02:00.032116 | 9.305 | 165.543 | \-701.034 | | … | … | … | … | | 2023-04-05 00:05:00.032116 | \-4.384 | 504.205 | \-1005.883 | | 2023-04-05 00:05:10.032116 | \-10.432 | 506.615 | \-952.28 | | 2023-04-05 00:05:20.032116 | 20.341 | 542.513 | \-917.164 | | 2023-04-05 00:05:30.032116 | 3.113 | 587.247 | \-858.536 | | 2023-04-05 00:05:40.032116 | 1.212 | 542.287 | \-901.735 | | 2023-04-05 00:05:50.032116 | 3.997 | 184.424 | \-833.991 | | 2023-04-05 00:06:00.032116 | \-0.477 | 180.863 | \-825.373 | | 2023-04-05 00:06:10.032116 | \-3.77 | 525.716 | \-887.492 | | 2023-04-05 00:06:20.032116 | \-5.273 | 434.96 | \-1004.985 | | 2023-04-05 00:06:30.032116 | \-4.487 | 570.354 | \-837.517 | | 2023-04-05 00:06:40.032116 | \-6.186 | 565.936 | \-838.518 | | 2023-04-05 00:06:50.032116 | 3.351 | 534.445 | \-870.112 | \[7\]: import matplotlib.pyplot as plt plt.plot(df\['Local Timestamp'\], df\['TOB Imbalance'\]) plt.plot(df\['Local Timestamp'\], df\['0.5% Imbalance'\]) plt.plot(df\['Local Timestamp'\], df\['1% Imbalance'\]) plt.legend(\['TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\]) \[7\]: ![../_images/tutorials_Working_with_Market_Depth_and_Trades_9_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Working_with_Market_Depth_and_Trades_9_1.png) Display last trades between the step[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-last-trades-between-the-step "Permalink to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[31\]: from hftbacktest import COL\_EXCH\_TIMESTAMP, COL\_SIDE, COL\_PRICE, COL\_QTY @njit def print\_trades(hbt): while hbt.elapse(60 \* 1e6): print('-------------------------------------------------------------------------------') print('current\_timestamp:', hbt.current\_timestamp) num \= 0 for trade in hbt.last\_trades: if num \> 10: print('...') break print( 'exch\_timestamp:', trade\[COL\_EXCH\_TIMESTAMP\], 'buy' if trade\[COL\_SIDE\] \== 1 else 'sell', trade\[COL\_QTY\], '@', trade\[COL\_PRICE\] ) num += 1 hbt.clear\_last\_trades() return True \[32\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod, trade\_list\_size\=10\_000 ) print\_trades(hbt) \------------------------------------------------------------------------------- current\_timestamp: 1680652860032116 exch\_timestamp: 1680652804962000.0 sell 0.001 @ 28155.1 exch\_timestamp: 1680652804964000.0 buy 0.012 @ 28155.2 exch\_timestamp: 1680652804966000.0 buy 0.002 @ 28155.2 exch\_timestamp: 1680652804968000.0 buy 0.003 @ 28155.2 exch\_timestamp: 1680652804981000.0 sell 0.019 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.004 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.001 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.02 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.013 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.002 @ 28155.1 exch\_timestamp: 1680652804981000.0 sell 0.001 @ 28155.0 ... ------------------------------------------------------------------------------- current\_timestamp: 1680652920032116 exch\_timestamp: 1680652860008000.0 buy 1.887 @ 28150.8 exch\_timestamp: 1680652860008000.0 buy 0.139 @ 28150.8 exch\_timestamp: 1680652860009000.0 buy 0.053 @ 28150.8 exch\_timestamp: 1680652860580000.0 buy 0.007 @ 28150.8 exch\_timestamp: 1680652860605000.0 buy 0.063 @ 28150.8 exch\_timestamp: 1680652860659000.0 sell 0.006 @ 28150.7 exch\_timestamp: 1680652860659000.0 sell 0.011 @ 28150.7 exch\_timestamp: 1680652860674000.0 buy 0.018 @ 28150.8 exch\_timestamp: 1680652860696000.0 sell 0.009 @ 28150.7 exch\_timestamp: 1680652860696000.0 sell 0.061 @ 28150.7 exch\_timestamp: 1680652860821000.0 sell 0.05 @ 28150.7 ... ------------------------------------------------------------------------------- current\_timestamp: 1680652980032116 exch\_timestamp: 1680652920308000.0 buy 0.013 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.001 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.02 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.036 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.002 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.011 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.004 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.028 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.026 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.035 @ 28144.2 exch\_timestamp: 1680652920308000.0 buy 0.026 @ 28144.2 ... ------------------------------------------------------------------------------- current\_timestamp: 1680653040032116 exch\_timestamp: 1680652980140000.0 buy 0.086 @ 28150.0 exch\_timestamp: 1680652980140000.0 buy 0.002 @ 28150.0 exch\_timestamp: 1680652980140000.0 buy 0.012 @ 28150.0 exch\_timestamp: 1680652980140000.0 sell 0.02 @ 28149.9 exch\_timestamp: 1680652980140000.0 sell 0.488 @ 28149.9 exch\_timestamp: 1680652980140000.0 sell 0.195 @ 28149.9 exch\_timestamp: 1680652980140000.0 sell 0.791 @ 28149.9 exch\_timestamp: 1680652980213000.0 buy 0.007 @ 28150.0 exch\_timestamp: 1680652980239000.0 sell 0.062 @ 28149.9 exch\_timestamp: 1680652980266000.0 buy 0.001 @ 28150.0 exch\_timestamp: 1680652980271000.0 buy 0.006 @ 28150.0 ... ------------------------------------------------------------------------------- current\_timestamp: 1680653100032116 exch\_timestamp: 1680653040100000.0 buy 0.007 @ 28145.8 exch\_timestamp: 1680653040117000.0 buy 0.001 @ 28145.8 exch\_timestamp: 1680653040117000.0 buy 0.006 @ 28145.8 exch\_timestamp: 1680653040119000.0 buy 0.014 @ 28145.8 exch\_timestamp: 1680653040120000.0 buy 0.008 @ 28145.8 exch\_timestamp: 1680653040535000.0 sell 0.2 @ 28145.7 exch\_timestamp: 1680653040652000.0 buy 0.004 @ 28145.8 exch\_timestamp: 1680653040652000.0 buy 0.02 @ 28145.8 exch\_timestamp: 1680653040652000.0 buy 0.216 @ 28145.8 exch\_timestamp: 1680653040652000.0 buy 0.904 @ 28145.8 exch\_timestamp: 1680653040838000.0 buy 0.008 @ 28145.8 ... ------------------------------------------------------------------------------- current\_timestamp: 1680653160032116 exch\_timestamp: 1680653100101000.0 sell 0.02 @ 28140.5 exch\_timestamp: 1680653100182000.0 buy 0.007 @ 28140.6 exch\_timestamp: 1680653100197000.0 buy 0.005 @ 28140.6 exch\_timestamp: 1680653100230000.0 sell 0.02 @ 28140.5 exch\_timestamp: 1680653100303000.0 buy 0.007 @ 28140.6 exch\_timestamp: 1680653100341000.0 buy 0.017 @ 28140.6 exch\_timestamp: 1680653100358000.0 sell 0.009 @ 28140.5 exch\_timestamp: 1680653100358000.0 sell 0.041 @ 28140.5 exch\_timestamp: 1680653100628000.0 buy 0.008 @ 28140.6 exch\_timestamp: 1680653100706000.0 sell 0.004 @ 28140.5 exch\_timestamp: 1680653100707000.0 sell 0.001 @ 28140.5 ... \[32\]: True Rolling Volume-Weighted Average Price[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Rolling-Volume-Weighted-Average-Price "Permalink to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[10\]: @njit def rolling\_vwap(hbt, out): buy\_amount\_bin \= np.zeros(100\_000, np.float64) buy\_qty\_bin \= np.zeros(100\_000, np.float64) sell\_amount\_bin \= np.zeros(100\_000, np.float64) sell\_qty\_bin \= np.zeros(100\_000, np.float64) idx \= 0 last\_trade\_price \= np.nan while hbt.elapse(10 \* 1e6): for trade in hbt.last\_trades: if trade\[COL\_SIDE\] \== 1: buy\_amount\_bin\[idx\] += trade\[COL\_PRICE\] \* trade\[COL\_QTY\] buy\_qty\_bin\[idx\] += trade\[COL\_QTY\] else: sell\_amount\_bin\[idx\] += trade\[COL\_PRICE\] \* trade\[COL\_QTY\] sell\_qty\_bin\[idx\] += trade\[COL\_QTY\] hbt.clear\_last\_trades() idx += 1 if idx \>= 1: vwap10sec \= np.divide( buy\_amount\_bin\[idx \- 1\] + sell\_amount\_bin\[idx \- 1\], buy\_qty\_bin\[idx \- 1\] + sell\_qty\_bin\[idx \- 1\] ) else: vwap10sec \= np.nan if idx \>= 6: vwap1m \= np.divide( np.sum(buy\_amount\_bin\[idx \- 6:idx\]) + np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\]) + np.sum(sell\_qty\_bin\[idx \- 6:idx\]) ) buy\_vwap1m \= np.divide(np.sum(buy\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\])) sell\_vwap1m \= np.divide(np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(sell\_qty\_bin\[idx \- 6:idx\])) else: vwap1m \= np.nan buy\_vwap1m \= np.nan sell\_vwap1m \= np.nan out.append((hbt.current\_timestamp, vwap10sec, vwap1m, buy\_vwap1m, sell\_vwap1m)) return True \[11\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod, trade\_list\_size\=1\_000\_000 ) tup\_ty \= Tuple((float64, float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) rolling\_vwap(hbt, out) \[11\]: True \[12\]: df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', '10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='us') ) df \[12\]: shape: (41, 5) | Local Timestamp | 10-sec VWAP | 1-min VWAP | 1-min Buy VWAP | 1-min Sell VWAP | | --- | --- | --- | --- | --- | | datetime\[μs\] | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | | 2023-04-05 00:00:10.032116 | 28152.252939 | NaN | NaN | NaN | | 2023-04-05 00:00:20.032116 | 28155.780263 | NaN | NaN | NaN | | 2023-04-05 00:00:30.032116 | 28158.015906 | NaN | NaN | NaN | | 2023-04-05 00:00:40.032116 | 28155.813019 | NaN | NaN | NaN | | 2023-04-05 00:00:50.032116 | 28156.783983 | NaN | NaN | NaN | | 2023-04-05 00:01:00.032116 | 28154.537949 | 28155.548141 | 28155.915523 | 28155.223338 | | 2023-04-05 00:01:10.032116 | 28148.396356 | 28153.368132 | 28154.890947 | 28152.319296 | | 2023-04-05 00:01:20.032116 | 28146.825218 | 28152.039289 | 28153.901905 | 28150.83132 | | 2023-04-05 00:01:30.032116 | 28144.455386 | 28150.627706 | 28151.934098 | 28149.81655 | | 2023-04-05 00:01:40.032116 | 28144.432187 | 28149.560181 | 28150.540685 | 28148.888516 | | 2023-04-05 00:01:50.032116 | 28144.340373 | 28147.623107 | 28148.057738 | 28147.411383 | | 2023-04-05 00:02:00.032116 | 28142.735181 | 28146.012256 | 28146.175163 | 28145.918324 | | … | … | … | … | … | | 2023-04-05 00:05:00.032116 | 28140.810824 | 28142.411037 | 28142.7071 | 28142.244012 | | 2023-04-05 00:05:10.032116 | 28139.182176 | 28141.264196 | 28142.570677 | 28140.811206 | | 2023-04-05 00:05:20.032116 | 28138.95427 | 28140.263581 | 28140.273185 | 28140.260421 | | 2023-04-05 00:05:30.032116 | 28139.49472 | 28139.946069 | 28139.43771 | 28140.107281 | | 2023-04-05 00:05:40.032116 | 28139.720917 | 28139.82033 | 28139.223683 | 28140.005444 | | 2023-04-05 00:05:50.032116 | 28140.151155 | 28139.602697 | 28139.723243 | 28139.493739 | | 2023-04-05 00:06:00.032116 | 28143.477257 | 28139.927132 | 28140.206712 | 28139.635456 | | 2023-04-05 00:06:10.032116 | 28142.323272 | 28140.338009 | 28140.35086 | 28140.321614 | | 2023-04-05 00:06:20.032116 | 28138.54843 | 28140.391805 | 28140.716124 | 28140.025369 | | 2023-04-05 00:06:30.032116 | 28137.611515 | 28139.958313 | 28139.962781 | 28139.951255 | | 2023-04-05 00:06:40.032116 | 28140.107487 | 28139.965883 | 28139.964373 | 28139.9681 | | 2023-04-05 00:06:50.032116 | 28139.449719 | 28139.746092 | 28139.841886 | 28139.673659 | \[13\]: plt.plot(df\['Local Timestamp'\], df\['10-sec VWAP'\]) plt.plot(df\['Local Timestamp'\], df\['1-min VWAP'\]) plt.plot(df\['Local Timestamp'\], df\['1-min Buy VWAP'\]) plt.plot(df\['Local Timestamp'\], df\['1-min Sell VWAP'\]) plt.legend(\['10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\]) \[13\]: ![../_images/tutorials_Working_with_Market_Depth_and_Trades_17_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Working_with_Market_Depth_and_Trades_17_1.png) --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Index * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#M) | [**N**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#N) | [**O**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#P) | [**Q**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#Q) | [**R**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#T) | [**U**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#U) | [**V**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/py-v2.0.0/genindex.html#W) A - | | | | --- | --- | | * [ALL\_ASSETS (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.ALL_ASSETS)

* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency) | * [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size)

* [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_depth() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_depth)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency) | * [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.FILLED)

* [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.FOK) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.LIMIT)

* [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset)

* [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord) | * [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.price_tick) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD) | * [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade)

* [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats) | * [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # High-Frequency Grid Trading — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * High-Frequency Grid Trading * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/High-Frequency%20Grid%20Trading.ipynb) * * * High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading "Permalink to this heading") ==================================================================================================================================================================================== **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Plain High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/High-Frequency%20Grid%20Trading.html#Plain-High-Frequency-Grid-Trading "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ This is a high-frequency version of Grid Trading that keeps posting orders on grids centered around the mid-price, maintaining a fixed interval and a set number of grids. \[3\]: from numba import njit import pandas as pd import numpy as np from numba.typed import Dict from hftbacktest import NONE, NEW, HftBacktest, GTX, FeedLatency, SquareProbQueueModel, BUY, SELL, Linear, Stat, reset @njit def gridtrading(hbt, stat): max\_position \= 5 grid\_interval \= hbt.tick\_size \* 10 grid\_num \= 20 half\_spread \= hbt.tick\_size \* 20 \# Running interval in microseconds while hbt.elapse(100\_000): \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders() mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 bid\_order\_begin \= np.floor((mid\_price \- half\_spread) / grid\_interval) \* grid\_interval ask\_order\_begin \= np.ceil((mid\_price + half\_spread) / grid\_interval) \* grid\_interval order\_qty \= 0.1 last\_order\_id \= \-1 \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position: for i in range(grid\_num): bid\_order\_begin \-= i \* grid\_interval bid\_order\_tick \= round(bid\_order\_begin / hbt.tick\_size) \# Do not post buy orders above the best bid. if bid\_order\_tick \> hbt.best\_bid\_tick: continue \# order price in tick is used as order id. new\_bid\_orders\[bid\_order\_tick\] \= bid\_order\_begin for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) last\_order\_id \= order.order\_id for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) last\_order\_id \= order\_id \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position: for i in range(grid\_num): ask\_order\_begin += i \* grid\_interval ask\_order\_tick \= round(ask\_order\_begin / hbt.tick\_size) \# Do not post sell orders below the best ask. if ask\_order\_tick < hbt.best\_ask\_tick: continue \# order price in tick is used as order id. new\_ask\_orders\[ask\_order\_tick\] \= ask\_order\_begin for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) last\_order\_id \= order.order\_id for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: if not hbt.wait\_order\_response(last\_order\_id): return False \# Records the current state for stat calculation. stat.record(hbt) return True \[2\]: hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) Load data/ethusdt\_20221003.npz \[3\]: %%time gridtrading(hbt, stat.recorder) Load data/ethusdt\_20221004.npz Load data/ethusdt\_20221005.npz Load data/ethusdt\_20221006.npz Load data/ethusdt\_20221007.npz CPU times: user 3min 58s, sys: 6.03 s, total: 4min 4s Wall time: 4min 5s \[3\]: True \[4\]: stat.summary(capital\=15\_000) \=========== Summary =========== Sharpe ratio: 20.9 Sortino ratio: 22.4 Risk return ratio: 211.5 Annualised return: 330.53 % Max. draw down: 1.56 % The number of trades per day: 5954 Avg. daily trading volume: 595 Avg. daily trading amount: 798115 Max leverage: 0.52 Median leverage: 0.21 ![../_images/tutorials_High-Frequency_Grid_Trading_5_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_High-Frequency_Grid_Trading_5_1.png) High-Frequency Grid Trading with Skewing[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading-with-Skewing "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- By incorporating position-based skewing, the strategy’s risk-adjusted returns can be improved. \[5\]: @njit def gridtrading(hbt, stat, skew): max\_position \= 5 grid\_interval \= hbt.tick\_size \* 10 grid\_num \= 20 half\_spread \= hbt.tick\_size \* 20 \# Running interval in microseconds while hbt.elapse(100\_000): \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders() mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 reservation\_price \= mid\_price \- skew \* hbt.position \* hbt.tick\_size bid\_order\_begin \= np.floor((reservation\_price \- half\_spread) / grid\_interval) \* grid\_interval ask\_order\_begin \= np.ceil((reservation\_price + half\_spread) / grid\_interval) \* grid\_interval order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.lot\_size) \* hbt.lot\_size last\_order\_id \= \-1 \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position: \# hbt.position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_order\_begin \-= i \* grid\_interval bid\_order\_tick \= round(bid\_order\_begin / hbt.tick\_size) \# Do not post buy orders above the best bid. if bid\_order\_tick \> hbt.best\_bid\_tick: continue \# order price in tick is used as order id. new\_bid\_orders\[bid\_order\_tick\] \= bid\_order\_begin for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) last\_order\_id \= order.order\_id for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) last\_order\_id \= order\_id \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position: \# hbt.position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_order\_begin += i \* grid\_interval ask\_order\_tick \= round(ask\_order\_begin / hbt.tick\_size) \# Do not post sell orders below the best ask. if ask\_order\_tick < hbt.best\_ask\_tick: continue \# order price in tick is used as order id. new\_ask\_orders\[ask\_order\_tick\] \= ask\_order\_begin for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) last\_order\_id \= order.order\_id for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) last\_order\_id \= order\_id \# All order requests are considered to be requested at the same time. \# Waits until one of the order responses is received. if last\_order\_id \>= 0: if not hbt.wait\_order\_response(last\_order\_id): return False \# Records the current state for stat calculation. stat.record(hbt) return True Weak skew \[6\]: reset( hbt, \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) skew \= 1 gridtrading(hbt, stat.recorder, skew) stat.summary(capital\=15\_000) Load data/ethusdt\_20221003.npz Load data/ethusdt\_20221004.npz Load data/ethusdt\_20221005.npz Load data/ethusdt\_20221006.npz Load data/ethusdt\_20221007.npz =========== Summary =========== Sharpe ratio: 18.0 Sortino ratio: 17.5 Risk return ratio: 169.2 Annualised return: 166.77 % Max. draw down: 0.99 % The number of trades per day: 6488 Avg. daily trading volume: 648 Avg. daily trading amount: 870207 Max leverage: 0.50 Median leverage: 0.10 ![../_images/tutorials_High-Frequency_Grid_Trading_9_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_High-Frequency_Grid_Trading_9_1.png) Strong skew \[7\]: reset( hbt, \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) skew \= 10 gridtrading(hbt, stat.recorder, skew) stat.summary(capital\=15\_000) Load data/ethusdt\_20221003.npz Load data/ethusdt\_20221004.npz Load data/ethusdt\_20221005.npz Load data/ethusdt\_20221006.npz Load data/ethusdt\_20221007.npz =========== Summary =========== Sharpe ratio: 29.3 Sortino ratio: 33.4 Risk return ratio: 735.4 Annualised return: 100.30 % Max. draw down: 0.14 % The number of trades per day: 6636 Avg. daily trading volume: 663 Avg. daily trading amount: 889749 Max leverage: 0.51 Median leverage: 0.02 ![../_images/tutorials_High-Frequency_Grid_Trading_11_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_High-Frequency_Grid_Trading_11_1.png) Multiple Assets[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/High-Frequency%20Grid%20Trading.html#Multiple-Assets "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------ You might need to find the proper parameters for each asset to achieve better performance. As an example, here it uses single parameters set to demonstrate how the performance of a combination of multiple assets will be. \[8\]: @njit def gridtrading(hbt, stat, half\_spread, grid\_interval, skew, order\_qty): grid\_num \= 20 max\_position \= grid\_num \* order\_qty \# Running interval in microseconds while hbt.elapse(100\_000): mid\_price \= (hbt.best\_bid + hbt.best\_ask) / 2.0 normalized\_position \= hbt.position / order\_qty bid\_depth \= half\_spread + skew \* normalized\_position ask\_depth \= half\_spread \- skew \* normalized\_position bid\_price \= min(mid\_price \- bid\_depth, hbt.best\_bid) ask\_price \= max(mid\_price + ask\_depth, hbt.best\_ask) grid\_interval \= max(np.round(half\_spread / hbt.tick\_size) \* hbt.tick\_size, hbt.tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) \# Records the current state for stat calculation. stat.record(hbt) return True \[9\]: from hftbacktest import IntpOrderLatency, LogProbQueueModel2, COL\_PRICE, COL\_SIDE latency\_data \= np.concatenate( \[np.load('../latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=LogProbQueueModel2(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name) ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] half\_spread \= mid \* 0.0008 grid\_interval \= mid \* 0.0008 skew \= mid \* 0.000025 gridtrading(hbt, stat.recorder, half\_spread, grid\_interval, skew, order\_qty) np.savez( 'stats/{}\_stat\_grid\_multi'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[10\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[11\]: from matplotlib import pyplot as plt equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('stats/{}\_stat\_grid.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 10 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[11\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_16_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_High-Frequency_Grid_Trading_16_1.png) --- # Impact of Order Latency — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Impact of Order Latency * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Impact%20of%20Order%20Latency.ipynb) * * * Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Impact%20of%20Order%20Latency.html#Impact-of-Order-Latency "Permalink to this heading") ========================================================================================================================================================================== This example illustrates the impact of order latency on the performance of the strategy. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: from numba import njit import numpy as np from numba.typed import Dict from hftbacktest import ( HftBacktest, NONE, NEW, GTX, BUY, SELL, ConstantLatency, FeedLatency, IntpOrderLatency, SquareProbQueueModel, Linear, Stat ) @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, stat): arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + .5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 order\_qty \= 1 max\_position \= 20 grid\_num \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= (c1 + 1 / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 bid\_depth \= half\_spread + skew \* hbt.position ask\_depth \= half\_spread \- skew \* hbt.position \# If the depth is invalid, set a large spread to prevent execution. if not np.isfinite(bid\_depth): bid\_depth \= 1\_000 if not np.isfinite(ask\_depth): ask\_depth \= 1\_000 bid\_price \= min(round(mid\_price\_tick \- bid\_depth), hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(round(mid\_price\_tick + ask\_depth), hbt.best\_ask\_tick) \* hbt.tick\_size grid\_interval \= round(max(half\_spread, 1)) \* hbt.tick\_size bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position: for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position: for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. stat.record(hbt) Order Latency from Feed Latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Feed-Latency "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[2\]: hbt \= HftBacktest( \[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz',\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/ethusdt\_20230331\_eod.npz', trade\_list\_size\=10\_000 ) stat \= Stat(hbt) gridtrading\_glft\_mm(hbt, stat.recorder) stat.summary(capital\=25\_000) Load data/ethusdt\_20230401.npz Load data/ethusdt\_20230402.npz Load data/ethusdt\_20230403.npz Load data/ethusdt\_20230404.npz Load data/ethusdt\_20230405.npz =========== Summary =========== Sharpe ratio: 4.6 Sortino ratio: 3.3 Risk return ratio: 43.1 Annualised return: 119.45 % Max. draw down: 2.77 % The number of trades per day: 3212 Avg. daily trading volume: 3212 Avg. daily trading amount: 5886441 Max leverage: 3.40 Median leverage: 0.22 ![../_images/tutorials_Impact_of_Order_Latency_3_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Impact_of_Order_Latency_3_1.png) Historical Order Latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Impact%20of%20Order%20Latency.html#Historical-Order-Latency "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[3\]: latency\_data \= np.concatenate( \[np.load('../latency/ethusdt\_{}\_latency.npz'.format(date))\['data'\] for date in range(20230401, 20230406)\] ) hbt \= HftBacktest( \[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz',\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/ethusdt\_20230331\_eod.npz', trade\_list\_size\=10\_000 ) stat \= Stat(hbt) gridtrading\_glft\_mm(hbt, stat.recorder) stat.summary(capital\=25\_000) Load data/ethusdt\_20230401.npz Load data/ethusdt\_20230402.npz Load data/ethusdt\_20230403.npz Load data/ethusdt\_20230404.npz Load data/ethusdt\_20230405.npz =========== Summary =========== Sharpe ratio: 0.4 Sortino ratio: 0.3 Risk return ratio: 2.8 Annualised return: 11.03 % Max. draw down: 4.00 % The number of trades per day: 3493 Avg. daily trading volume: 3493 Avg. daily trading amount: 6401297 Max leverage: 2.47 Median leverage: 0.22 ![../_images/tutorials_Impact_of_Order_Latency_5_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Impact_of_Order_Latency_5_1.png) Order Latency from Amplified Feed Latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Amplified-Feed-Latency "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: hbt \= HftBacktest( \[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz',\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(entry\_latency\_mul\=4, resp\_latency\_mul\=3), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/ethusdt\_20230331\_eod.npz', trade\_list\_size\=10\_000 ) stat \= Stat(hbt) gridtrading\_glft\_mm(hbt, stat.recorder) stat.summary(capital\=25\_000) Load data/ethusdt\_20230401.npz Load data/ethusdt\_20230402.npz Load data/ethusdt\_20230403.npz Load data/ethusdt\_20230404.npz Load data/ethusdt\_20230405.npz =========== Summary =========== Sharpe ratio: 0.2 Sortino ratio: 0.2 Risk return ratio: 1.8 Annualised return: 6.66 % Max. draw down: 3.61 % The number of trades per day: 3193 Avg. daily trading volume: 3193 Avg. daily trading amount: 5849525 Max leverage: 4.57 Median leverage: 0.22 ![../_images/tutorials_Impact_of_Order_Latency_7_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Impact_of_Order_Latency_7_1.png) --- # Order Fill — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Order Fill * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/order_fill.rst) * * * Order Fill[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#order-fill "Permalink to this heading") =================================================================================================================== Exchange Models[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#exchange-models "Permalink to this heading") ----------------------------------------------------------------------------------------------------------------------------- HftBacktest is a market-data replay-based backtesting tool, which means your order cannot make any changes to the simulated market, no market impact is considered. Therefore, one of the most important assumptions is that your order is small enough not to make any market impact. In the end, you must test it in a live market with real market participants and adjust your backtesting based on the discrepancies between the backtesting results and the live outcomes. Hftbacktest offers two types of exchange simulation. [NoPartialFillExchange](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#nopartialfillexchange) is the default exchange simulation where no partial fills occur. [PartialFillExchange](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#partialfillexchange) is the extended exchange simulation that accounts for partial fills in specific cases. Since the market-data replay-based backtesting cannot alter the market, some partial fill cases may still be unrealistic, such as taking market liquidity. This is because even if your order takes market liquidity, the replayed market data’s market depth and trades cannot change. It is essential to understand the underlying assumptions in each backtesting simulation. ### NoPartialFillExchange[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#nopartialfillexchange "Permalink to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#conditions-for-full-execution "Permalink to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price * Your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price * Your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#liquidity-taking-order "Permalink to this heading") > Regardless of the quantity at the best, liquidity-taking orders will be fully executed at the best. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. #### Usage[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#usage "Permalink to this heading") from hftbacktest import NoPartialFillExchange hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, exchange\_model\=NoPartialFillExchange, \# Default asset\_type\=Linear ) ### PartialFillExchange[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#partialfillexchange "Permalink to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#id1 "Permalink to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price #### Conditions for Partial Execution[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#conditions-for-partial-execution "Permalink to this heading") Buy order in the order book * Filled by (remaining) sell trade quantity: your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Filled by (remaining) buy trade quantity: your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#id2 "Permalink to this heading") > Liquidity-taking orders will be executed based on the quantity of the order book, even though the best price and quantity do not change due to your execution. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. #### Usage[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#id3 "Permalink to this heading") from hftbacktest import PartialFillExchange hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, exchange\_model\=PartialFillExchange, asset\_type\=Linear ) Queue Models[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#queue-models "Permalink to this heading") ----------------------------------------------------------------------------------------------------------------------- Knowing your order’s queue position is important to achieve accurate order fill simulation in backtesting depending on the liquidity of an order book and trading activities. If an exchange doesn’t provide Market-By-Order, you have to guess it by modeling. HftBacktest currently only supports Market-By-Price that is most crypto exchanges provide and it provides the following queue position models for order fill simulation. Please refer to the details at [Queue Models](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html) . ![_images/liquidity-and-trade-activities.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/liquidity-and-trade-activities.png) ### RiskAverseQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#riskaversequeuemodel "Permalink to this heading") This model is the most conservative model in terms of the chance of fill in the queue. The decrease in quantity by cancellation or modification in the order book happens only at the tail of the queue so your order queue position doesn’t change. The order queue position will be advanced only if a trade happens at the price. from hftbacktest import RiskAverseQueueModel hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=RiskAverseQueueModel() \# Default asset\_type\=Linear ) ### ProbQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#probqueuemodel "Permalink to this heading") Based on a probability model according to your current queue position, the decrease in quantity happens at both before and after the queue position. So your queue position is also advanced according to the probability. This model is implemented as described in * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) * [https://rigtorp.se/2013/06/08/estimating-order-queue-position.html](https://rigtorp.se/2013/06/08/estimating-order-queue-position.html) By default, three variations are provided. These three models have different probability profiles. ![_images/probqueuemodel.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/probqueuemodel.png) The function f = log(1 + x) exhibits a different probability profile depending on the total quantity at the price level, unlike power functions. ![_images/probqueuemodel_log.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/probqueuemodel_log.png) ![_images/probqueuemodel2.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/probqueuemodel2.png) ![_images/probqueuemodel3.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/probqueuemodel3.png) When you set the function f, it should be as follows. * The probability at 0 should be 0 because if the order is at the head of the queue, all decreases should happen after the order. \* The probability at 1 should be 1 because if the order is at the tail of the queue, all decreases should happen before the order. You can see the comparison of the models [here](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Probability%20Queue%20Models.html) . #### LogProbQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#logprobqueuemodel "Permalink to this heading") from hftbacktest import LogProbQueueModel hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=LogProbQueueModel() asset\_type\=Linear ) #### IdentityProbQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#identityprobqueuemodel "Permalink to this heading") from hftbacktest import IdentityProbQueueModel hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=IdentityProbQueueModel() asset\_type\=Linear ) #### SquareProbQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#squareprobqueuemodel "Permalink to this heading") from hftbacktest import SquareProbQueueModel hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=SquareProbQueueModel() asset\_type\=Linear ) #### PowerProbQueueModel[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#powerprobqueuemodel "Permalink to this heading") from hftbacktest import PowerProbQueueModel hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=PowerProbQueueModel(3) asset\_type\=Linear ) ### ProbQueueModel2[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#probqueuemodel2 "Permalink to this heading") This model is a variation of the [ProbQueueModel](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#probqueuemodel) that changes the probability calculation to f(back) / f(front + back) from f(back) / (f(front) + f(back)). #### LogProbQueueModel2[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#logprobqueuemodel2 "Permalink to this heading") from hftbacktest import LogProbQueueModel2 hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=LogProbQueueModel2() asset\_type\=Linear ) #### PowerProbQueueModel2[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#powerprobqueuemodel2 "Permalink to this heading") from hftbacktest import PowerProbQueueModel2 hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=PowerProbQueueModel2(3) asset\_type\=Linear ) ### ProbQueueModel3[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#probqueuemodel3 "Permalink to this heading") This model is a variation of the [ProbQueueModel](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#probqueuemodel) that changes the probability calculation to 1 - f(front / (front + back)) from f(back) / (f(front) + f(back)). #### PowerProbQueueModel3[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#powerprobqueuemodel3 "Permalink to this heading") from hftbacktest import PowerProbQueueModel3 hbt \= HftBacktest( data, tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(latency\_data), queue\_model\=PowerProbQueueModel3(3) asset\_type\=Linear ) ### Implement a custom probability queue position model[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#implement-a-custom-probability-queue-position-model "Permalink to this heading") @jitclass class CustomProbQueueModel(ProbQueueModel): def f(self, x): \# todo: custom formula return x \*\* 3 ### Implement a custom queue model[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#implement-a-custom-queue-model "Permalink to this heading") You need to implement `numba` `jitclass` that has four methods: `new`, `trade`, `depth`, `is_filled` See [Queue position model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/models/queue.py) in detail. @jitclass class CustomQueuePositionModel: def \_\_init\_\_(self): pass def new(self, order, proc): \# todo: when a new order is submitted. pass def trade(self, order, qty, proc): \# todo: when a trade happens. pass def depth(self, order, prev\_qty, new\_qty, proc): \# todo: when the order book quantity at the price is changed. pass def is\_filled(self, order, proc): \# todo: check if a given order is filled. return False def reset(self): pass References[](https://hftbacktest.readthedocs.io/en/v1.8.4/order_fill.html#references "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------- This is initially implemented as described in the following articles. * [http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf](http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf) * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) --- # Backtester — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Backtester * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/reference/backtester.rst) * * * Backtester[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#backtester "Permalink to this heading") ============================================================================================================================= _class_ SingleAssetHftBacktest(_local_, _exch_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest "Permalink to this definition") Single Asset HftBacktest. Warning This has to be constructed by [`HftBacktest()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/initialization.html#hftbacktest.HftBacktest "hftbacktest.HftBacktest") . Parameters: * **local** – Local processor. * **exch** – Exchange processor. run[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.run "Permalink to this definition") Whether a backtest has finished. current\_timestamp[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.current_timestamp "Permalink to this definition") Current timestamp _property_ position[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.position "Permalink to this definition") Current position. _property_ balance[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.balance "Permalink to this definition") Current balance.. _property_ orders[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.orders "Permalink to this definition") Orders dictionary. _property_ tick\_size[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.tick_size "Permalink to this definition") Tick size _property_ lot\_size[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.lot_size "Permalink to this definition") Lot size _property_ high\_ask\_tick[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.high_ask_tick "Permalink to this definition") The highest ask price in the market depth in tick. _property_ low\_bid\_tick[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.low_bid_tick "Permalink to this definition") The lowest bid price in the market depth in tick. _property_ best\_bid\_tick[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_bid_tick "Permalink to this definition") The best bid price in tick. _property_ best\_ask\_tick[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_ask_tick "Permalink to this definition") The best ask price in tick. _property_ best\_bid[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_bid "Permalink to this definition") The best bid price. _property_ best\_ask[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_ask "Permalink to this definition") The best ask price. _property_ bid\_depth[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.bid_depth "Permalink to this definition") Bid market depth. _property_ ask\_depth[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.ask_depth "Permalink to this definition") Ask market depth. _property_ mid[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.mid "Permalink to this definition") Mid-price of BBO. _property_ equity[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.equity "Permalink to this definition") Current equity value. _property_ last\_trade[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.last_trade "Permalink to this definition") Last market trade. If `None`, no last market trade. _property_ last\_trades[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.last_trades "Permalink to this definition") An array of last market trades. submit\_buy\_order(_order\_id_, _price_, _qty_, _time\_in\_force_, _order\_type\=0_, _wait\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.submit_buy_order) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.submit_buy_order "Permalink to this definition") Places a buy order. Parameters: * **order\_id** (_int64_) – The unique order ID; there should not be any existing order with the same ID on both local and exchange sides. * **price** (_float64_) – Order price. * **qty** (_float64_) – Quantity to buy. * **time\_in\_force** (_int64_) – Available Time-In-Force options vary depending on the exchange model. See to the exchange model for details. * `GTX`: Post-only * `GTC`: Good ‘till Cancel * `FOK`: Fill or Kill * `IOC`: Immediate or Cancel * **order\_type** (_int64_) – Currently, only `LIMIT` is supported. To simulate a `MARKET` order, set the price very high. * **wait** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If `True`, wait until the order placement response is received. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. submit\_sell\_order(_order\_id_, _price_, _qty_, _time\_in\_force_, _order\_type\=0_, _wait\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.submit_sell_order) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.submit_sell_order "Permalink to this definition") Places a sell order. Parameters: * **order\_id** (_int64_) – The unique order ID; there should not be any existing order with the same ID on both local and exchange sides. * **price** (_float64_) – Order price. * **qty** (_float64_) – Quantity to sell. * **time\_in\_force** (_int64_) – Available Time-In-Force options vary depending on the exchange model. See to the exchange model for details. * `GTX`: Post-only * `GTC`: Good ‘till Cancel * `FOK`: Fill or Kill * `IOC`: Immediate or Cancel * **order\_type** (_int64_) – Currently, only `LIMIT` is supported. To simulate a `MARKET` order, set the price very low. * **wait** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If `True`, wait until the order placement response is received. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. modify(_order\_id_, _price_, _qty_, _wait\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.modify) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.modify "Permalink to this definition") Modify the specified order. * If the adjusted total quantity(leaves\_qty + executed\_qty) is less than or equal to the quantity already executed, the order will be considered expired. Be aware that this adjustment doesn’t affect the remaining quantity in the market, it only changes the total quantity. * Modified orders will be reordered in the match queue. Parameters: * **order\_id** (_int64_) – Order ID to modify. * **price** (_float64_) – Order price. * **qty** (_float64_) – Quantity to sell. * **wait** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If `True`, wait until the order placement response is received. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. cancel(_order\_id_, _wait\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.cancel) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.cancel "Permalink to this definition") Cancel the specified order. Parameters: * **order\_id** (_int64_) – Order ID to cancel. * **wait** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If `True`, wait until the order placement response is received. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. wait\_order\_response(_order\_id_, _timeout\=\-1_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.wait_order_response) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.wait_order_response "Permalink to this definition") Wait for the specified order response by order ID. Parameters: * **order\_id** (_int64_) – The order ID to wait for. * **timeout** (_int64_) – Maximum waiting time; The default value of \-1 indicates no timeout. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. wait\_next\_feed(_include\_order\_resp_, _timeout\=\-1_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.wait_next_feed) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.wait_next_feed "Permalink to this definition") Waits until the next feed is received. Parameters: * **include\_order\_resp** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – Whether to include order responses in the feed to wait for. * **timeout** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – Maximum waiting time; The default value of \-1 indicates no timeout. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. clear\_inactive\_orders()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.clear_inactive_orders) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.clear_inactive_orders "Permalink to this definition") Clear inactive(`CANCELED`, `FILLED`, `EXPIRED`, or `REJECTED`) orders from the local `orders` dictionary. clear\_last\_trades()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.clear_last_trades) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.clear_last_trades "Permalink to this definition") Clears the last trades(market trades) from the buffer. get\_user\_data(_event_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.get_user_data) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.get_user_data "Permalink to this definition") Retrieve custom user event data. Parameters: **event** (_int64_) – Event identifier. Refer to the data documentation for details on incorporating custom user data with the market feed data. Returns: The latest event data for the specified event. elapse(_duration_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.elapse) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.elapse "Permalink to this definition") Elapses the specified duration. Parameters: **duration** (_float64_) – Duration to elapse. Unit should be the same as the feed data’s timestamp unit. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. goto(_timestamp_, _wait\_order\_response\=\-1_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/backtest.html#SingleAssetHftBacktest.goto) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.goto "Permalink to this definition") Goes to a specified timestamp. This method moves to the specified timestamp, updating the backtesting state to match the corresponding time. If `wait_order_response` is provided, the method will stop and return when it receives the response for the specified order. Parameters: * **timestamp** (_float64_) – The target timestamp to go to. The timestamp unit should be the same as the feed data’s timestamp unit. * **wait\_order\_response** (_int64_) – Order ID to wait for; the default value is `WAIT_ORDER_RESPONSE_NONE`, which means not waiting for any order response. Returns: `True` if the method reaches the specified timestamp within the data. If the end of the data is reached before the specified timestamp, it returns `False`. --- # Data Validation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Data Validation * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/reference/data_validation.rst) * * * Data Validation[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#module-hftbacktest.data.validation "Permalink to this heading") =============================================================================================================================================================== convert\_from\_struct\_arr(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#convert_from_struct_arr) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.convert_from_struct_arr "Permalink to this definition") Converts the structured array that can be used in Rust hftbacktest into the 2D ndarray currently used in Python hftbacktest. Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") ) – the structured array to be converted. Returns: Converted 2D ndarray. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") convert\_to\_struct\_arr(_data_, _add\_exch\_local\_ev\=True_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#convert_to_struct_arr) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.convert_to_struct_arr "Permalink to this definition") Converts the 2D ndarray currently used in Python hftbacktest into the structured array that can be used in Rust hftbacktest. Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") ) – 2D ndarray to be converted. * **add\_exch\_local\_ev** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If this is set to True, EXCH\_EVENT and LOCAL\_EVENT flags will be added to the ‘ev’ event field based on the validity of each timestamp. Set to True only when converting existing data into the new format. Returns: Converted structured array. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") correct(_data_, _base\_latency_, _tick\_size\=None_, _lot\_size\=None_, _err\_bound\=1e-08_, _method\='separate'_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#correct) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct "Permalink to this definition") Validates the specified data and automatically corrects negative latency and unordered rows. See [`validate_data()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.validate_data "hftbacktest.data.validation.validate_data") , [`correct_local_timestamp()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_local_timestamp "hftbacktest.data.validation.correct_local_timestamp") , [`correct_exch_timestamp()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp "hftbacktest.data.validation.correct_exch_timestamp") , and [`correct_exch_timestamp_adjust()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp_adjust "hftbacktest.data.validation.correct_exch_timestamp_adjust") . Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__ScalarType__\]__\]_ _|_ [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") ) – Data to be checked and corrected. * **base\_latency** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") ) – The value to be added to the feed latency. See [`correct_local_timestamp()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_local_timestamp "hftbacktest.data.validation.correct_local_timestamp") . * **tick\_size** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – Minimum price increment for the specified data. * **lot\_size** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – Minimum order quantity for the specified data. * **err\_bound** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") ) – Error bound used to verify if the specified `tick_size` or `lot_size` aligns with the price and quantity. * **method** ([_Literal_](https://docs.python.org/3.8/library/typing.html#typing.Literal "(in Python v3.8)") _\[__'separate'__,_ _'adjust'__\]_) – The method to correct reversed exchange timestamp events. * `separate`: Use [`correct_local_timestamp()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_local_timestamp "hftbacktest.data.validation.correct_local_timestamp") . * `adjust`: Use [`correct_exch_timestamp_adjust()`](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp_adjust "hftbacktest.data.validation.correct_exch_timestamp_adjust") . Returns: Corrected data Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") \[[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ , [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ \[_ScalarType_\]\] | [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") correct\_event\_order(_data_, _sorted\_exch\_index_, _sorted\_local\_index_, _add\_exch\_local\_ev_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#correct_event_order) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_event_order "Permalink to this definition") Corrects exchange timestamps that are reversed by splitting each row into separate events, ordered by both exchange and local timestamps, through duplication. See `data` for details. Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") ) – Data to be reordered. * **sorted\_exch\_index** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") ) – Index of data sorted by exchange timestamp. * **sorted\_local\_index** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") ) – Index of data sorted by local timestamp. * **add\_exch\_local\_ev** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If this is set to True, EXCH\_EVENT and LOCAL\_EVENT flags will be added to the event field based on the validity of each timestamp. Returns: Adjusted data with corrected exchange timestamps. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") correct\_exch\_timestamp(_data_, _num\_corr_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#correct_exch_timestamp) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp "Permalink to this definition") Corrects exchange timestamps that are reversed by splitting each row into separate events, ordered by both exchange and local timestamps, through duplication. See `data` for details. Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__ScalarType__\]__\]_ _|_ [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") ) – Data to be corrected. * **num\_corr** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The number of rows to be corrected. Returns: Adjusted data with corrected exchange timestamps. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") \[[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ , [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ \[_ScalarType_\]\] | [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") correct\_exch\_timestamp\_adjust(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#correct_exch_timestamp_adjust) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp_adjust "Permalink to this definition") Corrects reversed exchange timestamps by adjusting the local timestamp value for proper ordering. It sorts the data by exchange timestamp and fixes out-of-order local timestamps by setting their value to the previous value, ensuring correct ordering. Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__ScalarType__\]__\]_ _|_ [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") ) – Data to be corrected. Returns: Adjusted data with corrected exchange timestamps. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") \[[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ , [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ \[_ScalarType_\]\] | [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") correct\_local\_timestamp(_data_, _base\_latency_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#correct_local_timestamp) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_local_timestamp "Permalink to this definition") Adjusts the local timestamp if the feed latency is negative by offsetting the maximum negative latency value as follows: feed\_latency \= local\_timestamp \- exch\_timestamp adjusted\_local\_timestamp \= local\_timestamp + min(feed\_latency, 0) + base\_latency Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__ScalarType__\]__\]_ _|_ [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") ) – Data to be corrected. * **base\_latency** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") ) – Due to discrepancies in system time between the exchange and the local machine, latency may be measured inaccurately, resulting in negative latency values. The conversion process automatically adjusts for positive latency but may still produce zero latency cases. By adding `base_latency`, more realistic values can be obtained. Unit should be the same as the feed data’s timestamp unit. Returns: Adjusted data with corrected timestamps Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") \[[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ , [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ \[_ScalarType_\]\] | [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") validate\_data(_data_, _tick\_size\=None_, _lot\_size\=None_, _err\_bound\=1e-08_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/data/validation.html#validate_data) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.validate_data "Permalink to this definition") Validates the specified data for the following aspects, excluding user events. Validation results will be printed out: > * Ensures data’s price aligns with tick\_size. > > * Ensures data’s quantity aligns with lot\_size. > > * Ensures data’s local timestamp is ordered. > > * Ensures data’s exchange timestamp is ordered. > Parameters: * **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.8/library/typing.html#typing.Any "(in Python v3.8)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__ScalarType__\]__\]_ _|_ [_DataFrame_](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html#pandas.DataFrame "(in pandas v2.2.2)") ) – Data to be validated. * **tick\_size** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – Minimum price increment for the given asset. * **lot\_size** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – Minimum order quantity for the given asset. * **err\_bound** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") ) – Error bound used to verify if the specified `tick_size` or `lot_size` aligns with the price and quantity. Returns: The number of rows with reversed exchange timestamps. Return type: [int](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") --- # Stat — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Stat * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/reference/stat.rst) * * * Stat[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#stat "Permalink to this heading") =========================================================================================================== _class_ Stat(_hbt_, _utc\=True_, _unit\='us'_, _allocated\=100000_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat "Permalink to this definition") Calculates performance statistics and generates a summary of performance metrics. Parameters: * **hbt** (_HftBacktest_) – An instance of the HftBacktest class. * **utc** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If `True`, timestamps are in UTC. * **unit** ([_Literal_](https://docs.python.org/3.8/library/typing.html#typing.Literal "(in Python v3.8)") _\[__'s'__,_ _'ms'__,_ _'us'__,_ _'ns'__\]_) – The unit of the timestamp. * **allocated** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The preallocated size of recorded time series. annualised\_return(_denom\=None_, _include\_fee\=True_, _trading\_days\=365_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.annualised_return) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.annualised_return "Permalink to this definition") Calculates annualised return. Parameters: * **denom** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – If provided, annualised return will be calculated in percentage terms by dividing by the specified denominator. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. * **trading\_days** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The number of trading days per year used for annualisation. Returns: Annaulised return. daily\_trade\_amount()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.daily_trade_amount) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_amount "Permalink to this definition") Retrieves the average value of daily trades. Returns: Average value of daily trades. daily\_trade\_num()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.daily_trade_num) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_num "Permalink to this definition") Retrieves the average number of daily trades. Returns: Average number of daily trades. daily\_trade\_volume()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.daily_trade_volume) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_volume "Permalink to this definition") Retrieves the average quantity of daily trades. Returns: Average quantity of daily trades. datetime()[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.datetime) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.datetime "Permalink to this definition") Converts and returns a DateTime series from the timestamp. Returns: DateTime series by converting from the timestamp. drawdown(_resample\=None_, _include\_fee\=True_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.drawdown) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.drawdown "Permalink to this definition") Retrieves Draw Down time-series. Parameters: * **resample** ([_str_](https://docs.python.org/3.8/library/stdtypes.html#str "(in Python v3.8)") _|_ _None_) – The resampling period, such as ‘1s’, ‘5min’. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. Returns: Draw down time-series. equity(_resample\=None_, _include\_fee\=True_, _datetime\=True_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.equity) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.equity "Permalink to this definition") Calculates equity values. Parameters: * **resample** ([_str_](https://docs.python.org/3.8/library/stdtypes.html#str "(in Python v3.8)") _|_ _None_) – If provided, equity values will be resampled based on the specified period. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. * **datetime** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, the timestamp is converted to a DateTime, which takes a long time. If you want fast computation, set it to `False`. Returns: the calculated equity values. maxdrawdown(_denom\=None_, _include\_fee\=True_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.maxdrawdown) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.maxdrawdown "Permalink to this definition") Retrieves Maximum Draw Down. Parameters: * **denom** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – If provided, MDD will be calculated in percentage terms by dividing by the specified denominator. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. Returns: Maximum Draw Down. _property_ recorder[](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.recorder "Permalink to this definition") Returns a `Recorder` instance to record performance statistics. riskreturnratio(_include\_fee\=True_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.riskreturnratio) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.riskreturnratio "Permalink to this definition") Calculates Risk-Return Ratio, which is Annualized Return / Maximum Draw Down over the entire period. Parameters: **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. Returns: Risk-Return Ratio sharpe(_resample_, _include\_fee\=True_, _trading\_days\=365_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.sharpe) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.sharpe "Permalink to this definition") Calculates the Sharpe Ratio without considering benchmark rates. Parameters: * **resample** ([_str_](https://docs.python.org/3.8/library/stdtypes.html#str "(in Python v3.8)") ) – The resampling period, such as ‘1s’, ‘5min’. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. * **trading\_days** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The number of trading days per year used for annualisation. Returns: The calculated Sharpe Ratio. sortino(_resample_, _include\_fee\=True_, _trading\_days\=365_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.sortino) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.sortino "Permalink to this definition") Calculates Sortino Ratio. Parameters: * **resample** ([_str_](https://docs.python.org/3.8/library/stdtypes.html#str "(in Python v3.8)") ) – The resampling period, such as ‘1s’, ‘5min’. * **include\_fee** ([_bool_](https://docs.python.org/3.8/library/functions.html#bool "(in Python v3.8)") ) – If set to `True`, fees will be included in the calculation; otherwise, fees will be excluded. * **trading\_days** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The number of trading days per year used for annualisation. Returns: Sortino Ratio summary(_capital\=None_, _resample\='5min'_, _trading\_days\=365_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Stat.summary) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.summary "Permalink to this definition") Generates a summary of performance metrics. Parameters: * **capital** ([_float_](https://docs.python.org/3.8/library/functions.html#float "(in Python v3.8)") _|_ _None_) – The initial capital investment for the strategy. If provided, it is used as the denominator to calculate annualized return and MDD in percentage terms. Otherwise, absolute values are displayed. * **resample** ([_str_](https://docs.python.org/3.8/library/stdtypes.html#str "(in Python v3.8)") ) – The resampling period, such as ‘1s’, ‘5min’. * **trading\_days** ([_int_](https://docs.python.org/3.8/library/functions.html#int "(in Python v3.8)") ) – The number of trading days per year used for annualisation. _class_ Recorder(_timestamp_, _mid_, _balance_, _position_, _fee_, _trade\_num_, _trade\_qty_, _trade\_amount_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Recorder) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Recorder "Permalink to this definition") Parameters: * **timestamp** (_ListType__(__int64__)_) – * **mid** (_ListType__(__float64__)_) – * **balance** (_ListType__(__float64__)_) – * **position** (_ListType__(__float64__)_) – * **fee** (_ListType__(__float64__)_) – * **trade\_num** (_ListType__(__int64__)_) – * **trade\_qty** (_ListType__(__float64__)_) – * **trade\_amount** (_ListType__(__float64__)_) – record(_hbt_)[\[source\]](https://hftbacktest.readthedocs.io/en/v1.8.4/_modules/hftbacktest/stat.html#Recorder.record) [](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Recorder.record "Permalink to this definition") Records the current stats. Parameters: **hbt** – An instance of the HftBacktest class. --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Debugging Backtesting and Live Discrepancies * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/debugging_backtesting_and_live_discrepancies.rst.txt) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Link to this heading") ======================================================================================================================================================================================================================= Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as `PowerProbQueueModel` series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#order-fill-prob-queue-model) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * JIT Compilation Overhead * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/jit_compilation_overhead.rst.txt) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/jit_compilation_overhead.html#jit-compilation-overhead "Link to this heading") =========================================================================================================================================================== HftBacktest takes advantage of Numba’s capabilities, relying on Numba JIT’ed classes. As a result, importing HftBacktest requires JIT compilation, which may take a few seconds. Additionally, the strategy function needs to be JIT’ed’ for performant backtesting, which also takes time to compile. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature. See the example below. from numba import njit \# May take a few seconds from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest \# Enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. asset \= ( BacktestAsset() .linear\_asset(1.0) .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .no\_partial\_fill\_exchange() .intp\_order\_latency(\[\ 'data/latency\_20221003.npz',\ 'data/latency\_20221004.npz',\ 'data/latency\_20221005.npz',\ 'data/latency\_20221006.npz',\ 'data/latency\_20221007.npz'\ \]) .power\_prob\_queue\_model3(3.0) .tick\_size(0.01) .lot\_size(0.001) .trading\_value\_fee\_model(0.0002, 0.0007) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) algo(arguments, hbt) --- # Unknown { "cells": \[ { "cell\_type": "markdown", "id": "79d9aaef", "metadata": {}, "source": \[ "# Working with Market Depth and Trades" \] }, { "cell\_type": "markdown", "id": "d0585554", "metadata": {}, "source": \[ "## Display 3-depth" \] }, { "cell\_type": "code", "execution\_count": 1, "id": "8bbb4d6f-7a84-4fec-ac56-24a3d2b3d78a", "metadata": {}, "outputs": \[\], "source": \[ "from numba import njit\\n", "\\n", "@njit\\n", "def print\_3depth(hbt):\\n", " while hbt.elapse(60\_000\_000\_000) == 0:\\n", " print('current\_timestamp:', hbt.current\_timestamp)\\n", "\\n", " # Gets the market depth for the first asset, in the same order as when you created the backtest.\\n", " depth = hbt.depth(0)\\n", "\\n", " # a key of bid\_depth or ask\_depth is price in ticks.\\n", " # (integer) price\_tick = rice / tick\_size\\n", " i = 0\\n", " for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100):\\n", " qty = depth.ask\_qty\_at\_tick(price\_tick)\\n", " if qty > 0:\\n", " print(\\n", " 'ask: ',\\n", " qty,\\n", " '@',\\n", " np.round(price\_tick \* depth.tick\_size, 1)\\n", " )\\n", " \\n", " i += 1\\n", " if i == 3:\\n", " break\\n", " i = 0\\n", " for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick - 100, 0), -1):\\n", " qty = depth.bid\_qty\_at\_tick(price\_tick)\\n", " if qty > 0:\\n", " print(\\n", " 'bid: ',\\n", " qty,\\n", " '@',\\n", " np.round(price\_tick \* depth.tick\_size, 1)\\n", " )\\n", " \\n", " i += 1\\n", " if i == 3:\\n", " break\\n", " return True" \] }, { "cell\_type": "code", "execution\_count": 2, "id": "0aab2f88", "metadata": {}, "outputs": \[\], "source": \[ "import numpy as np\\n", "\\n", "btcusdt\_20240809 = np.load('usdm/btcusdt\_20240809.npz')\['data'\]\\n", "btcusdt\_20240808\_eod = np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\]" \] }, { "cell\_type": "code", "execution\_count": 3, "id": "79afc7c0", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "current\_timestamp: 1723161661500000000\\n", "ask: 1.759 @ 61594.2\\n", "ask: 0.006 @ 61594.4\\n", "ask: 0.114 @ 61595.2\\n", "bid: 3.526 @ 61594.1\\n", "bid: 0.016 @ 61594.0\\n", "bid: 0.002 @ 61593.9\\n", "current\_timestamp: 1723161721500000000\\n", "ask: 2.575 @ 61576.6\\n", "ask: 0.004 @ 61576.7\\n", "ask: 0.455 @ 61577.0\\n", "bid: 2.558 @ 61576.5\\n", "bid: 0.002 @ 61576.0\\n", "bid: 0.515 @ 61575.5\\n", "current\_timestamp: 1723161781500000000\\n", "ask: 0.131 @ 61629.7\\n", "ask: 0.005 @ 61630.1\\n", "ask: 0.005 @ 61630.5\\n", "bid: 5.742 @ 61629.6\\n", "bid: 0.247 @ 61629.4\\n", "bid: 0.034 @ 61629.3\\n", "current\_timestamp: 1723161841500000000\\n", "ask: 0.202 @ 61621.6\\n", "ask: 0.002 @ 61622.5\\n", "ask: 0.003 @ 61622.6\\n", "bid: 3.488 @ 61621.5\\n", "bid: 0.86 @ 61620.0\\n", "bid: 0.248 @ 61619.6\\n", "current\_timestamp: 1723161901500000000\\n", "ask: 1.397 @ 61584.0\\n", "ask: 0.832 @ 61585.1\\n", "ask: 0.132 @ 61586.0\\n", "bid: 3.307 @ 61583.9\\n", "bid: 0.01 @ 61583.8\\n", "bid: 0.002 @ 61582.0\\n" \] } \], "source": \[ "from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest\\n", "\\n", "asset = (\\n", " BacktestAsset()\\n", " .data(btcusdt\_20240809)\\n", " .initial\_snapshot(btcusdt\_20240808\_eod)\\n", " .linear\_asset(1.0) \\n", " .constant\_latency(10\_000\_000, 10\_000\_000)\\n", " .risk\_adverse\_queue\_model() \\n", " .no\_partial\_fill\_exchange()\\n", " .trading\_value\_fee\_model(0.0002, 0.0007)\\n", " .tick\_size(0.1)\\n", " .lot\_size(0.001)\\n", ")\\n", "\\n", "hbt = HashMapMarketDepthBacktest(\[asset\])\\n", "\\n", "print\_3depth(hbt)\\n", "\\n", "\_ = hbt.close()" \] }, { "cell\_type": "markdown", "id": "354e8fec-95cb-4205-b820-f934fa2d5836", "metadata": {}, "source": \[ "## Efficient Market Depth Access\\n", "\\n", "\`ROIVectorMarketDepth\` provides more efficient market depth access through a vector that holds a limited price range of interest. The backtester using this feature can be created by \`ROIVectorMarketDepthBacktest\`." \] }, { "cell\_type": "code", "execution\_count": 4, "id": "580a566c-6520-40b0-90ec-c2a933017b52", "metadata": {}, "outputs": \[\], "source": \[ "from numba import njit\\n", "\\n", "@njit\\n", "def print\_3depth\_fast(hbt):\\n", " roi\_lb\_tick = int(round(30000 / 0.1))\\n", " roi\_ub\_tick = int(round(90000 / 0.1))\\n", " \\n", " while hbt.elapse(60\_000\_000\_000) == 0:\\n", " print('current\_timestamp:', hbt.current\_timestamp)\\n", "\\n", " # Gets the market depth for the first asset, in the same order as when you created the backtest.\\n", " depth = hbt.depth(0)\\n", "\\n", " # a key of bid\_depth or ask\_depth is price in ticks.\\n", " # (integer) price\_tick = price / tick\_size\\n", " i = 0\\n", " for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100):\\n", " # depth.ask\_depth returns the ask depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick),\\n", " # containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick.\\n", " # Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick.\\n", " if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick:\\n", " continue\\n", " t = price\_tick - roi\_lb\_tick\\n", " qty = depth.ask\_depth\[t\]\\n", " if qty > 0:\\n", " print(\\n", " 'ask: ',\\n", " qty,\\n", " '@',\\n", " np.round(price\_tick \* depth.tick\_size, 1)\\n", " )\\n", " \\n", " i += 1\\n", " if i == 3:\\n", " break\\n", " i = 0\\n", " for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick - 100, 0), -1):\\n", " # depth.bid\_depth returns the bid depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick),\\n", " # containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick.\\n", " # Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick.\\n", " if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick:\\n", " continue\\n", " t = price\_tick - roi\_lb\_tick\\n", " qty = depth.bid\_depth\[t\]\\n", " if qty > 0:\\n", " print(\\n", " 'bid: ',\\n", " qty,\\n", " '@',\\n", " np.round(price\_tick \* depth.tick\_size, 1)\\n", " )\\n", " \\n", " i += 1\\n", " if i == 3:\\n", " break\\n", " return True" \] }, { "cell\_type": "code", "execution\_count": 5, "id": "94d2e7a7-9179-4380-8e18-661833a4b95f", "metadata": {}, "outputs": \[\], "source": \[ "from hftbacktest import ROIVectorMarketDepthBacktest\\n", "\\n", "asset = (\\n", " BacktestAsset()\\n", " .data(btcusdt\_20240809)\\n", " .initial\_snapshot(btcusdt\_20240808\_eod)\\n", " .linear\_asset(1.0) \\n", " .constant\_latency(10\_000\_000, 10\_000\_000)\\n", " .risk\_adverse\_queue\_model() \\n", " .no\_partial\_fill\_exchange()\\n", " .trading\_value\_fee\_model(0.0002, 0.0007)\\n", " .tick\_size(0.1)\\n", " .lot\_size(0.001)\\n", " # Sets the lower bound price for the range of interest in the market depth.\\n", " .roi\_lb(30000)\\n", " # Sets the upper bound price for the range of interest in the market depth.\\n", " .roi\_ub(90000)\\n", ")\\n", "\\n" \] }, { "cell\_type": "code", "execution\_count": 6, "id": "a8e2b8bc-eb4f-4401-9d1c-d4e5428708e4", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "current\_timestamp: 1723161661500000000\\n", "ask: 1.759 @ 61594.2\\n", "ask: 0.006 @ 61594.4\\n", "ask: 0.114 @ 61595.2\\n", "bid: 3.526 @ 61594.1\\n", "bid: 0.016 @ 61594.0\\n", "bid: 0.002 @ 61593.9\\n", "current\_timestamp: 1723161721500000000\\n", "ask: 2.575 @ 61576.6\\n", "ask: 0.004 @ 61576.7\\n", "ask: 0.455 @ 61577.0\\n", "bid: 2.558 @ 61576.5\\n", "bid: 0.002 @ 61576.0\\n", "bid: 0.515 @ 61575.5\\n", "current\_timestamp: 1723161781500000000\\n", "ask: 0.131 @ 61629.7\\n", "ask: 0.005 @ 61630.1\\n", "ask: 0.005 @ 61630.5\\n", "bid: 5.742 @ 61629.6\\n", "bid: 0.247 @ 61629.4\\n", "bid: 0.034 @ 61629.3\\n", "current\_timestamp: 1723161841500000000\\n", "ask: 0.202 @ 61621.6\\n", "ask: 0.002 @ 61622.5\\n", "ask: 0.003 @ 61622.6\\n", "bid: 3.488 @ 61621.5\\n", "bid: 0.86 @ 61620.0\\n", "bid: 0.248 @ 61619.6\\n", "current\_timestamp: 1723161901500000000\\n", "ask: 1.397 @ 61584.0\\n", "ask: 0.832 @ 61585.1\\n", "ask: 0.132 @ 61586.0\\n", "bid: 3.307 @ 61583.9\\n", "bid: 0.01 @ 61583.8\\n", "bid: 0.002 @ 61582.0\\n" \] } \], "source": \[ "hbt = ROIVectorMarketDepthBacktest(\[asset\])\\n", "\\n", "print\_3depth\_fast(hbt)\\n", "\\n", "\_ = hbt.close()" \] }, { "cell\_type": "markdown", "id": "a8f00571", "metadata": {}, "source": \[ "## Order Book Imbalance" \] }, { "cell\_type": "code", "execution\_count": 7, "id": "e8d55680", "metadata": {}, "outputs": \[\], "source": \[ "@njit\\n", "def orderbookimbalance(hbt, out):\\n", " roi\_lb\_tick = int(round(30000 / 0.1))\\n", " roi\_ub\_tick = int(round(90000 / 0.1))\\n", "\\n", " while hbt.elapse(10 \* 1e9) == 0:\\n", " depth = hbt.depth(0)\\n", " \\n", " mid\_price = (depth.best\_bid + depth.best\_ask) / 2.0\\n", " \\n", " sum\_ask\_qty\_50bp = 0.0\\n", " sum\_ask\_qty = 0.0\\n", " for price\_tick in range(depth.best\_ask\_tick, roi\_ub\_tick + 1):\\n", " if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick:\\n", " continue\\n", " t = price\_tick - roi\_lb\_tick\\n", " \\n", " ask\_price = price\_tick \* depth.tick\_size\\n", " depth\_from\_mid = (ask\_price - mid\_price) / mid\_price\\n", " if depth\_from\_mid > 0.01:\\n", " break\\n", " sum\_ask\_qty += depth.ask\_depth\[t\]\\n", " \\n", " if depth\_from\_mid <= 0.005:\\n", " sum\_ask\_qty\_50bp = sum\_ask\_qty\\n", " \\n", " \\n", " sum\_bid\_qty\_50bp = 0.0\\n", " sum\_bid\_qty = 0.0\\n", " for price\_tick in range(depth.best\_bid\_tick, roi\_lb\_tick - 1, -1):\\n", " if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick:\\n", " continue\\n", " t = price\_tick - roi\_lb\_tick\\n", " \\n", " bid\_price = price\_tick \* depth.tick\_size\\n", " depth\_from\_mid = (mid\_price - bid\_price) / mid\_price\\n", " if depth\_from\_mid > 0.01:\\n", " break\\n", " sum\_bid\_qty += depth.bid\_depth\[t\]\\n", " \\n", " if depth\_from\_mid <= 0.005:\\n", " sum\_bid\_qty\_50bp = sum\_bid\_qty\\n", " \\n", " imbalance\_50bp = sum\_bid\_qty\_50bp - sum\_ask\_qty\_50bp\\n", " imbalance\_1pct = sum\_bid\_qty - sum\_ask\_qty\\n", " imbalance\_tob = depth.bid\_depth\[depth.best\_bid\_tick - roi\_lb\_tick\] - depth.ask\_depth\[depth.best\_ask\_tick - roi\_lb\_tick\]\\n", " \\n", " out.append((hbt.current\_timestamp, imbalance\_tob, imbalance\_50bp, imbalance\_1pct)) \\n", " return True" \] }, { "cell\_type": "code", "execution\_count": 8, "id": "99a684f8", "metadata": {}, "outputs": \[\], "source": \[ "from numba.typed import List\\n", "from numba.types import Tuple, float64\\n", "\\n", "hbt = ROIVectorMarketDepthBacktest(\[asset\])\\n", "\\n", "tup\_ty = Tuple((float64, float64, float64, float64))\\n", "out = List.empty\_list(tup\_ty, allocated=100\_000)\\n", "\\n", "orderbookimbalance(hbt, out)\\n", "\\n", "\_ = hbt.close()" \] }, { "cell\_type": "code", "execution\_count": 9, "id": "be1b53b2", "metadata": {}, "outputs": \[ { "data": { "text/html": \[ "\ \ \\n", "shape: (30, 4)\ \ | Local Timestamp | TOB Imbalance | 0.5% Imbalance | 1% Imbalance |\ | --- | --- | --- | --- |\ | datetime\[ns\] | f64 | f64 | f64 |\ | --- | --- | --- | --- |\ | 2024-08-09 00:00:11.500 | 2.729 | \-1748.101 | \-3908.736 |\ | 2024-08-09 00:00:21.500 | 4.623 | \-1749.435 | \-3512.845 |\ | 2024-08-09 00:00:31.500 | \-6.465 | \-1259.897 | \-3357.755 |\ | 2024-08-09 00:00:41.500 | \-7.922 | \-1174.185 | \-3471.955 |\ | 2024-08-09 00:00:51.500 | \-2.484 | \-1147.597 | \-3461.48 |\ | … | … | … | … |\ | 2024-08-09 00:04:21.500 | 3.828 | \-1186.236 | \-3551.78 |\ | 2024-08-09 00:04:31.500 | \-1.35 | \-1332.379 | \-3517.854 |\ | 2024-08-09 00:04:41.500 | \-3.754 | \-1166.521 | \-2693.672 |\ | 2024-08-09 00:04:51.500 | \-2.525 | \-1188.56 | \-2716.914 |\ | 2024-08-09 00:05:01.500 | 1.91 | \-594.991 | \-2138.82 |\ \ " \], "text/plain": \[ "shape: (30, 4)\\n", "┌─────────────────────────┬───────────────┬────────────────┬──────────────┐\\n", "│ Local Timestamp ┆ TOB Imbalance ┆ 0.5% Imbalance ┆ 1% Imbalance │\\n", "│ --- ┆ --- ┆ --- ┆ --- │\\n", "│ datetime\[ns\] ┆ f64 ┆ f64 ┆ f64 │\\n", "╞═════════════════════════╪═══════════════╪════════════════╪══════════════╡\\n", "│ 2024-08-09 00:00:11.500 ┆ 2.729 ┆ -1748.101 ┆ -3908.736 │\\n", "│ 2024-08-09 00:00:21.500 ┆ 4.623 ┆ -1749.435 ┆ -3512.845 │\\n", "│ 2024-08-09 00:00:31.500 ┆ -6.465 ┆ -1259.897 ┆ -3357.755 │\\n", "│ 2024-08-09 00:00:41.500 ┆ -7.922 ┆ -1174.185 ┆ -3471.955 │\\n", "│ 2024-08-09 00:00:51.500 ┆ -2.484 ┆ -1147.597 ┆ -3461.48 │\\n", "│ … ┆ … ┆ … ┆ … │\\n", "│ 2024-08-09 00:04:21.500 ┆ 3.828 ┆ -1186.236 ┆ -3551.78 │\\n", "│ 2024-08-09 00:04:31.500 ┆ -1.35 ┆ -1332.379 ┆ -3517.854 │\\n", "│ 2024-08-09 00:04:41.500 ┆ -3.754 ┆ -1166.521 ┆ -2693.672 │\\n", "│ 2024-08-09 00:04:51.500 ┆ -2.525 ┆ -1188.56 ┆ -2716.914 │\\n", "│ 2024-08-09 00:05:01.500 ┆ 1.91 ┆ -594.991 ┆ -2138.82 │\\n", "└─────────────────────────┴───────────────┴────────────────┴──────────────┘" \] }, "execution\_count": 9, "metadata": {}, "output\_type": "execute\_result" } \], "source": \[ "import polars as pl\\n", "\\n", "df = pl.DataFrame(out).transpose()\\n", "df.columns = \['Local Timestamp', 'TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\]\\n", "df = df.with\_columns(\\n", " pl.from\_epoch('Local Timestamp', time\_unit='ns')\\n", ")\\n", "\\n", "df" \] }, { "cell\_type": "code", "execution\_count": 10, "id": "f3b43c9f", "metadata": {}, "outputs": \[ { "data": { "text/plain": \[ "\[\]" \] }, "execution\_count": 10, "metadata": {}, "output\_type": "execute\_result" }, { "data": { "image/png": "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", "text/plain": \[ "\ \ " \] }, "metadata": {}, "output\_type": "display\_data" } \], "source": \[ "from matplotlib import pyplot\\n", "\\n", "pyplot.plot(df\['Local Timestamp'\], df\['TOB Imbalance'\])\\n", "pyplot.plot(df\['Local Timestamp'\], df\['0.5% Imbalance'\])\\n", "pyplot.plot(df\['Local Timestamp'\], df\['1% Imbalance'\])" \] }, { "cell\_type": "markdown", "id": "d5a9d4a3", "metadata": {}, "source": \[ "## Display last trades between the step" \] }, { "cell\_type": "code", "execution\_count": 11, "id": "71dd4f1a", "metadata": {}, "outputs": \[\], "source": \[ "from hftbacktest import BUY\_EVENT\\n", "\\n", "@njit\\n", "def print\_trades(hbt):\\n", " while hbt.elapse(60 \* 1e9) == 0:\\n", " print('-------------------------------------------------------------------------------')\\n", " print('current\_timestamp:', hbt.current\_timestamp)\\n", "\\n", " # Gets the last trades occurring in the market, not the trades of our orders.\\n", " last\_trades = hbt.last\_trades(0)\\n", " \\n", " num = 0\\n", " for last\_trade in last\_trades:\\n", " if num > 10:\\n", " print('...')\\n", " break\\n", " print(\\n", " 'exch\_timestamp:',\\n", " last\_trade.exch\_ts,\\n", " 'buy' if (last\_trade.ev & BUY\_EVENT) == BUY\_EVENT else 'sell',\\n", " last\_trade.qty,\\n", " '@',\\n", " last\_trade.px\\n", " )\\n", " num += 1\\n", "\\n", " # To prevent accumulating all last trades, which may cause a slowdown,\\n", " # clear\_last\_trades needs to be called.\\n", " # After this, accessing \`last\_trades\` will cause a crash.\\n", " hbt.clear\_last\_trades(0)\\n", " return True" \] }, { "cell\_type": "code", "execution\_count": 12, "id": "0d37656a", "metadata": {}, "outputs": \[ { "name": "stdout", "output\_type": "stream", "text": \[ "-------------------------------------------------------------------------------\\n", "current\_timestamp: 1723161661500000000\\n", "exch\_timestamp: 1723161602372000000 buy 0.489 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.198 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.006 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.011 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.238 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.007 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.005 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8\\n", "exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8\\n", "...\\n", "-------------------------------------------------------------------------------\\n", "current\_timestamp: 1723161721500000000\\n", "exch\_timestamp: 1723161661697000000 sell 0.002 @ 61594.1\\n", "exch\_timestamp: 1723161661724000000 sell 0.002 @ 61594.1\\n", "exch\_timestamp: 1723161661751000000 buy 0.135 @ 61594.2\\n", "exch\_timestamp: 1723161661806000000 sell 1.328 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.006 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.32 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 0.032 @ 61594.1\\n", "exch\_timestamp: 1723161661806000000 sell 1.208 @ 61594.1\\n", "...\\n", "-------------------------------------------------------------------------------\\n", "current\_timestamp: 1723161781500000000\\n", "exch\_timestamp: 1723161721541000000 sell 0.002 @ 61576.5\\n", "exch\_timestamp: 1723161721574000000 buy 0.012 @ 61576.6\\n", "exch\_timestamp: 1723161721578000000 sell 0.003 @ 61576.5\\n", "exch\_timestamp: 1723161721583000000 buy 0.275 @ 61576.6\\n", "exch\_timestamp: 1723161721583000000 buy 0.469 @ 61576.6\\n", "exch\_timestamp: 1723161721585000000 buy 0.095 @ 61576.6\\n", "exch\_timestamp: 1723161721585000000 buy 0.102 @ 61576.6\\n", "exch\_timestamp: 1723161721585000000 buy 0.197 @ 61576.6\\n", "exch\_timestamp: 1723161721586000000 buy 0.13 @ 61576.6\\n", "exch\_timestamp: 1723161721587000000 buy 0.425 @ 61576.6\\n", "exch\_timestamp: 1723161721587000000 buy 0.324 @ 61576.6\\n", "...\\n", "-------------------------------------------------------------------------------\\n", "current\_timestamp: 1723161841500000000\\n", "exch\_timestamp: 1723161781628000000 sell 0.026 @ 61629.6\\n", "exch\_timestamp: 1723161781727000000 buy 0.011 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.05 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.006 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.007 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.075 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.065 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.247 @ 61629.7\\n", "exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7\\n", "...\\n", "-------------------------------------------------------------------------------\\n", "current\_timestamp: 1723161901500000000\\n", "exch\_timestamp: 1723161841561000000 buy 0.01 @ 61621.6\\n", "exch\_timestamp: 1723161841561000000 buy 0.006 @ 61621.6\\n", "exch\_timestamp: 1723161841561000000 buy 0.002 @ 61621.6\\n", "exch\_timestamp: 1723161841561000000 buy 0.022 @ 61621.6\\n", "exch\_timestamp: 1723161841561000000 buy 0.097 @ 61621.6\\n", "exch\_timestamp: 1723161841561000000 buy 0.024 @ 61621.6\\n", "exch\_timestamp: 1723161841564000000 buy 0.024 @ 61621.6\\n", "exch\_timestamp: 1723161841564000000 buy 0.014 @ 61621.6\\n", "exch\_timestamp: 1723161841565000000 buy 0.003 @ 61621.6\\n", "exch\_timestamp: 1723161841613000000 buy 0.002 @ 61622.5\\n", "exch\_timestamp: 1723161841613000000 buy 0.003 @ 61622.6\\n", "...\\n" \] } \], "source": \[ "asset = (\\n", " BacktestAsset()\\n", " .data(btcusdt\_20240809)\\n", " .initial\_snapshot(btcusdt\_20240808\_eod)\\n", " .linear\_asset(1.0) \\n", " .constant\_latency(10\_000\_000, 10\_000\_000)\\n", " .risk\_adverse\_queue\_model() \\n", " .no\_partial\_fill\_exchange()\\n", " .trading\_value\_fee\_model(0.0002, 0.0007)\\n", " .tick\_size(0.1)\\n", " .lot\_size(0.001)\\n", " # To retrieve the last trades, \`last\_trades\_capacity\` should be set.\\n", " .last\_trades\_capacity(1000)\\n", " .roi\_lb(30000)\\n", " .roi\_ub(90000)\\n", ")\\n", "\\n", "hbt = ROIVectorMarketDepthBacktest(\[asset\])\\n", "\\n", "print\_trades(hbt)\\n", "\\n", "\_ = hbt.close()" \] }, { "cell\_type": "markdown", "id": "a37694e1", "metadata": {}, "source": \[ "## Rolling Volume-Weighted Average Price" \] }, { "cell\_type": "code", "execution\_count": 13, "id": "002fa805", "metadata": {}, "outputs": \[\], "source": \[ "@njit\\n", "def rolling\_vwap(hbt, out):\\n", " buy\_amount\_bin = np.zeros(100\_000, np.float64)\\n", " buy\_qty\_bin = np.zeros(100\_000, np.float64)\\n", " sell\_amount\_bin = np.zeros(100\_000, np.float64)\\n", " sell\_qty\_bin = np.zeros(100\_000, np.float64)\\n", " \\n", " idx = 0\\n", " last\_trade\_price = np.nan\\n", " \\n", " while hbt.elapse(10 \* 1e9) == 0:\\n", " last\_trades = hbt.last\_trades(0)\\n", " \\n", " for last\_trade in last\_trades:\\n", " if (last\_trade.ev & BUY\_EVENT) == BUY\_EVENT:\\n", " buy\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty\\n", " buy\_qty\_bin\[idx\] += last\_trade.qty\\n", " else:\\n", " sell\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty\\n", " sell\_qty\_bin\[idx\] += last\_trade.qty\\n", " \\n", " hbt.clear\_last\_trades(0)\\n", " idx += 1\\n", "\\n", " if idx >= 1:\\n", " vwap10sec = np.divide(\\n", " buy\_amount\_bin\[idx - 1\] + sell\_amount\_bin\[idx - 1\], \\n", " buy\_qty\_bin\[idx - 1\] + sell\_qty\_bin\[idx - 1\]\\n", " )\\n", " else:\\n", " vwap10sec = np.nan\\n", " \\n", " if idx >= 6:\\n", " vwap1m = np.divide(\\n", " np.sum(buy\_amount\_bin\[idx - 6:idx\]) + np.sum(sell\_amount\_bin\[idx - 6:idx\]), \\n", " np.sum(buy\_qty\_bin\[idx - 6:idx\]) + np.sum(sell\_qty\_bin\[idx - 6:idx\])\\n", " )\\n", " buy\_vwap1m = np.divide(np.sum(buy\_amount\_bin\[idx - 6:idx\]), np.sum(buy\_qty\_bin\[idx - 6:idx\]))\\n", " sell\_vwap1m = np.divide(np.sum(sell\_amount\_bin\[idx - 6:idx\]), np.sum(sell\_qty\_bin\[idx - 6:idx\]))\\n", " else:\\n", " vwap1m = np.nan\\n", " buy\_vwap1m = np.nan\\n", " sell\_vwap1m = np.nan\\n", " \\n", " out.append((hbt.current\_timestamp, vwap10sec, vwap1m, buy\_vwap1m, sell\_vwap1m))\\n", " return True" \] }, { "cell\_type": "code", "execution\_count": 14, "id": "f90739a8", "metadata": {}, "outputs": \[\], "source": \[ "hbt = ROIVectorMarketDepthBacktest(\[asset\])\\n", "\\n", "tup\_ty = Tuple((float64, float64, float64, float64, float64))\\n", "out = List.empty\_list(tup\_ty, allocated=100\_000)\\n", "\\n", "rolling\_vwap(hbt, out)\\n", "\\n", "\_ = hbt.close()" \] }, { "cell\_type": "code", "execution\_count": 15, "id": "f2ab6bd3", "metadata": {}, "outputs": \[ { "data": { "text/html": \[ "\ \ \\n", "shape: (30, 5)\ \ | Local Timestamp | 10-sec VWAP | 1-min VWAP | 1-min Buy VWAP | 1-min Sell VWAP |\ | --- | --- | --- | --- | --- |\ | datetime\[ns\] | f64 | f64 | f64 | f64 |\ | --- | --- | --- | --- | --- |\ | 2024-08-09 00:00:11.500 | 61687.182976 | NaN | NaN | NaN |\ | 2024-08-09 00:00:21.500 | 61709.337576 | NaN | NaN | NaN |\ | 2024-08-09 00:00:31.500 | 61697.538054 | NaN | NaN | NaN |\ | 2024-08-09 00:00:41.500 | 61663.958879 | NaN | NaN | NaN |\ | 2024-08-09 00:00:51.500 | 61637.340621 | NaN | NaN | NaN |\ | … | … | … | … | … |\ | 2024-08-09 00:04:21.500 | 61643.009847 | 61624.459011 | 61626.495542 | 61622.549429 |\ | 2024-08-09 00:04:31.500 | 61670.795685 | 61635.877251 | 61638.362314 | 61632.48854 |\ | 2024-08-09 00:04:41.500 | 61643.108582 | 61641.846489 | 61648.672337 | 61636.032054 |\ | 2024-08-09 00:04:51.500 | 61614.723569 | 61640.490841 | 61647.769844 | 61634.372128 |\ | 2024-08-09 00:05:01.500 | 61584.697467 | 61637.334102 | 61642.209551 | 61632.12064 |\ \ " \], "text/plain": \[ "shape: (30, 5)\\n", "┌─────────────────────────┬──────────────┬──────────────┬────────────────┬─────────────────┐\\n", "│ Local Timestamp ┆ 10-sec VWAP ┆ 1-min VWAP ┆ 1-min Buy VWAP ┆ 1-min Sell VWAP │\\n", "│ --- ┆ --- ┆ --- ┆ --- ┆ --- │\\n", "│ datetime\[ns\] ┆ f64 ┆ f64 ┆ f64 ┆ f64 │\\n", "╞═════════════════════════╪══════════════╪══════════════╪════════════════╪═════════════════╡\\n", "│ 2024-08-09 00:00:11.500 ┆ 61687.182976 ┆ NaN ┆ NaN ┆ NaN │\\n", "│ 2024-08-09 00:00:21.500 ┆ 61709.337576 ┆ NaN ┆ NaN ┆ NaN │\\n", "│ 2024-08-09 00:00:31.500 ┆ 61697.538054 ┆ NaN ┆ NaN ┆ NaN │\\n", "│ 2024-08-09 00:00:41.500 ┆ 61663.958879 ┆ NaN ┆ NaN ┆ NaN │\\n", "│ 2024-08-09 00:00:51.500 ┆ 61637.340621 ┆ NaN ┆ NaN ┆ NaN │\\n", "│ … ┆ … ┆ … ┆ … ┆ … │\\n", "│ 2024-08-09 00:04:21.500 ┆ 61643.009847 ┆ 61624.459011 ┆ 61626.495542 ┆ 61622.549429 │\\n", "│ 2024-08-09 00:04:31.500 ┆ 61670.795685 ┆ 61635.877251 ┆ 61638.362314 ┆ 61632.48854 │\\n", "│ 2024-08-09 00:04:41.500 ┆ 61643.108582 ┆ 61641.846489 ┆ 61648.672337 ┆ 61636.032054 │\\n", "│ 2024-08-09 00:04:51.500 ┆ 61614.723569 ┆ 61640.490841 ┆ 61647.769844 ┆ 61634.372128 │\\n", "│ 2024-08-09 00:05:01.500 ┆ 61584.697467 ┆ 61637.334102 ┆ 61642.209551 ┆ 61632.12064 │\\n", "└─────────────────────────┴──────────────┴──────────────┴────────────────┴─────────────────┘" \] }, "execution\_count": 15, "metadata": {}, "output\_type": "execute\_result" } \], "source": \[ "df = pl.DataFrame(out).transpose()\\n", "df.columns = \['Local Timestamp', '10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\]\\n", "df = df.with\_columns(\\n", " pl.from\_epoch('Local Timestamp', time\_unit='ns')\\n", ")\\n", "\\n", "df" \] }, { "cell\_type": "code", "execution\_count": 16, "id": "f23d4a4e", "metadata": {}, "outputs": \[ { "data": { "text/plain": \[ "\[\]" \] }, "execution\_count": 16, "metadata": {}, "output\_type": "execute\_result" }, { "data": { "image/png": "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", "text/plain": \[ "\ \ " \] }, "metadata": {}, "output\_type": "display\_data" } \], "source": \[ "pyplot.plot(df\['Local Timestamp'\], df\['10-sec VWAP'\])\\n", "pyplot.plot(df\['Local Timestamp'\], df\['1-min VWAP'\])\\n", "pyplot.plot(df\['Local Timestamp'\], df\['1-min Buy VWAP'\])\\n", "pyplot.plot(df\['Local Timestamp'\], df\['1-min Sell VWAP'\])" \] } \], "metadata": { "kernelspec": { "display\_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language\_info": { "codemirror\_mode": { "name": "ipython", "version": 3 }, "file\_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert\_exporter": "python", "pygments\_lexer": "ipython3", "version": "3.10.14" } }, "nbformat": 4, "nbformat\_minor": 5 } --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Index * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/genindex) * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#M) | [**O**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#P) | [**R**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#T) | [**V**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/v1.8.4/genindex.html#W) A - | | | | --- | --- | | * [annualised\_return() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.annualised_return) | * [ask\_depth (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.ask_depth) | B - | | | | --- | --- | | * [BackwardFeedLatency (class in hftbacktest.models.latencies)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/order_latency_models.html#hftbacktest.models.latencies.BackwardFeedLatency)

* [balance (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.balance)

* [best\_ask (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_ask) | * [best\_ask\_tick (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_ask_tick)

* [best\_bid (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_bid)

* [best\_bid\_tick (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.best_bid_tick)

* [bid\_depth (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.bid_depth) | C - | | | | --- | --- | | * [cancel() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.cancel)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.clear_inactive_orders)

* [clear\_last\_trades() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.clear_last_trades)

* [ConstantLatency (class in hftbacktest.models.latencies)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/order_latency_models.html#hftbacktest.models.latencies.ConstantLatency)

* [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_from\_struct\_arr() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.convert_from_struct_arr) | * [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [convert\_to\_struct\_arr() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.convert_to_struct_arr)

* [correct() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct)

* [correct\_event\_order() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_event_order)

* [correct\_exch\_timestamp() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp)

* [correct\_exch\_timestamp\_adjust() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_exch_timestamp_adjust)

* [correct\_local\_timestamp() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (SingleAssetHftBacktest attribute)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily\_trade\_amount() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_amount)

* [daily\_trade\_num() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_num)

* [daily\_trade\_volume() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.daily_trade_volume) | * [datetime() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.datetime)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot)

* [drawdown() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.drawdown) | E - | | | | --- | --- | | * [elapse() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.elapse) | * [equity (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.equity)

* [equity() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.equity) | F - | | | | --- | --- | | * [FeedLatency (class in hftbacktest.models.latencies)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/order_latency_models.html#hftbacktest.models.latencies.FeedLatency) | * [ForwardFeedLatency (class in hftbacktest.models.latencies)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/order_latency_models.html#hftbacktest.models.latencies.ForwardFeedLatency) | G - | | | | --- | --- | | * [get\_user\_data() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.get_user_data) | * [goto() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.goto) | H - | | | | --- | --- | | * [HftBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/initialization.html#hftbacktest.HftBacktest)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot) | * hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis)

* hftbacktest.data.validation
* [module](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#module-hftbacktest.data.validation)

* [high\_ask\_tick (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.high_ask_tick) | I - | | | | --- | --- | | * [IdentityProbQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.IdentityProbQueueModel) | * [IntpOrderLatency (class in hftbacktest.models.latencies)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/order_latency_models.html#hftbacktest.models.latencies.IntpOrderLatency)

* [InverseAsset (class in hftbacktest.assettype)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/asset_types.html#hftbacktest.assettype.InverseAsset) | L - | | | | --- | --- | | * [last\_trade (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.last_trade)

* [last\_trades (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.last_trades)

* [LinearAsset (class in hftbacktest.assettype)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/asset_types.html#hftbacktest.assettype.LinearAsset) | * [LogProbQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.LogProbQueueModel)

* [LogProbQueueModel2 (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.LogProbQueueModel2)

* [lot\_size (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.lot_size)

* [low\_bid\_tick (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.low_bid_tick) | M - * [maxdrawdown() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.maxdrawdown) * [merge\_on\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_utilities.html#hftbacktest.data.merge_on_local_timestamp) * [mid (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.mid) * [modify() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.modify) * module * [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures) * [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) * [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot) * [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot) * [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) * [hftbacktest.data.validation](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#module-hftbacktest.data.validation) O - * [orders (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.orders) P - | | | | --- | --- | | * [position (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.position)

* [PowerProbQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.PowerProbQueueModel)

* [PowerProbQueueModel2 (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.PowerProbQueueModel2) | * [PowerProbQueueModel3 (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.PowerProbQueueModel3)

* [ProbQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.ProbQueueModel)

* [ProbQueueModel2 (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.ProbQueueModel2)

* [ProbQueueModel3 (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.ProbQueueModel3) | R - | | | | --- | --- | | * [record() (Recorder method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Recorder.record)

* [Recorder (class in hftbacktest.stat)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Recorder)

* [recorder (Stat property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.recorder) | * [reset() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/initialization.html#hftbacktest.reset)

* [RiskAverseQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.RiskAverseQueueModel)

* [riskreturnratio() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.riskreturnratio)

* [run (SingleAssetHftBacktest attribute)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.run) | S - | | | | --- | --- | | * [sharpe() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.sharpe)

* [SingleAssetHftBacktest (class in hftbacktest.backtest)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest)

* [sortino() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.sortino)

* [SquareProbQueueModel (class in hftbacktest.models.queue)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/queue_models.html#hftbacktest.models.queue.SquareProbQueueModel) | * [Stat (class in hftbacktest.stat)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat)

* [submit\_buy\_order() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.submit_buy_order)

* [submit\_sell\_order() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.submit_sell_order)

* [summary() (Stat method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/stat.html#hftbacktest.stat.Stat.summary) | T - * [tick\_size (SingleAssetHftBacktest property)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.tick_size) V - * [validate\_data() (in module hftbacktest.data.validation)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/data_validation.html#hftbacktest.data.validation.validate_data) W - | | | | --- | --- | | * [wait\_next\_feed() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.wait_next_feed) | * [wait\_order\_response() (SingleAssetHftBacktest method)](https://hftbacktest.readthedocs.io/en/v1.8.4/reference/backtester.html#hftbacktest.backtest.SingleAssetHftBacktest.wait_order_response) | --- # Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Data * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/data.rst) * * * Data[](https://hftbacktest.readthedocs.io/en/v1.8.4/data.html#data "Permalink to this heading") ================================================================================================= Please see [https://github.com/nkaz001/collect-binancefutures](https://github.com/nkaz001/collect-binancefutures) or [Data Preparation](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html) regarding collecting and converting the feed data. Format[](https://hftbacktest.readthedocs.io/en/v1.8.4/data.html#format "Permalink to this heading") ----------------------------------------------------------------------------------------------------- hftbacktest can digest a numpy file such as npz or npy and pickled pandas.DataFrame. The data has 6 columns as follows in the following order. * event: A type of event DEPTH\_EVENT \= 1 TRADE\_EVENT \= 2 DEPTH\_CLEAR\_EVENT \= 3 DEPTH\_SNAPSHOT\_EVENT \= 4 Event code above 100 is used for user-defined events. * exch\_timestamp: exchange timestamp * local\_timestamp: local timestamp that your system receives * side: side 1: Buy(Bid) -1: Sell(Ask) * price: price * qty: quantity ### Example[](https://hftbacktest.readthedocs.io/en/v1.8.4/data.html#example "Permalink to this heading") **Raw data** > 1676419207212527 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} **Normalized data** | event | exch\_timestamp | local\_timestamp | side | price | qty | | --- | --- | --- | --- | --- | --- | | 1 | 1676419205108000 | 1676419207212527 | 1 | 2218.8 | 0.603 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 5000.00 | 2.641 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22160.60 | 0.008 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22172.30 | 0.551 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22173.40 | 0.073 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22174.50 | 0.006 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22176.80 | 0.157 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22177.90 | 0.425 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22181.20 | 0.260 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22182.30 | 3.918 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22182.90 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22183.40 | 0.014 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22203.00 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22171.70 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22187.30 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22194.30 | 0.270 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22194.70 | 0.423 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22195.20 | 2.075 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22209.60 | 4.506 | | 2 | 1676419205116000 | 1676419207212584 | \-1 | 22177.90 | 0.001 | Validation[](https://hftbacktest.readthedocs.io/en/v1.8.4/data.html#validation "Permalink to this heading") ------------------------------------------------------------------------------------------------------------- Before you start backtesting, you should check if the data is valid. The data that is received from crypto exchanges needs data cleaning and validation. 1. All timestamp should be in the correct order. 2. You might find local\_timestamp is advanced to exch\_timestamp due to time-sync. As local\_timestamp - exch\_timestamp is used as latency, the value must be positive. 3. Even though local\_timestamp is in the correct order, exch\_timestamp can be in the incorrect order. See the following example. exch\_timestamp of depth feed is advanced to the prior trade feed even though depth feed is received after trade feed. > 1676419207212385 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} This should be converted into the following form. hftbacktest provides correct method to automatically correct this type of mess. > ... > 1676419207212385 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': \-1, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': \-1, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > ... > \-1 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > \-1 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212584 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} **Normalized data** | event | exch\_timestamp | local\_timestamp | side | price | qty | | --- | --- | --- | --- | --- | --- | | … | | | | | | | 2 | \-1 | 1676419207212385 | \-1 | 22177.90 | 0.300 | | 2 | \-1 | 1676419207212480 | \-1 | 22177.90 | 0.119 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 2218.8 | 0.603 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 5000.00 | 2.641 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22160.60 | 0.008 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22172.30 | 0.551 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22173.40 | 0.073 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22174.50 | 0.006 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22176.80 | 0.157 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22177.90 | 0.425 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22181.20 | 0.260 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22182.30 | 3.918 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22182.90 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22183.40 | 0.014 | | 1 | 1676419205108000 | 1676419207212527 | 1 | 22203.00 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22171.70 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22187.30 | 0.000 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22194.30 | 0.270 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22194.70 | 0.423 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22195.20 | 2.075 | | 1 | 1676419205108000 | 1676419207212527 | \-1 | 22209.60 | 4.506 | | … | | | | | | | 2 | 1676419205111000 | \-1 | \-1 | 22177.90 | 0.300 | | 2 | 1676419205111000 | \-1 | \-1 | 22177.90 | 0.119 | | 2 | 1676419206976000 | 1676419207212584 | \-1 | 22177.90 | 0.001 | | 2 | 1676419206976000 | 1676419207212621 | \-1 | 22177.90 | 0.005 | \-1 in exch\_timestamp means that the event is not processed on exchange-side logic such as order fill. \-1 in local\_timestamp means that the event is not recognized by the local. --- # Impact of Order Latency — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Impact of Order Latency * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Impact%20of%20Order%20Latency.ipynb.txt) * * * Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Impact%20of%20Order%20Latency.html#Impact-of-Order-Latency "Link to this heading") ======================================================================================================================================================================== This example illustrates the impact of order latency on the performance of the strategy. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, order\_qty, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) Order Latency from Feed Latency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Feed-Latency "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Please see [the tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) on generating artificial order latency data from feed latency. \[2\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20230401.npz',\ 'latency/feed\_latency\_20230402.npz',\ 'latency/feed\_latency\_20230403.npz',\ 'latency/feed\_latency\_20230404.npz',\ 'latency/feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[2\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.197608 | \-0.224204 | \-0.001021 | 0.060794 | 4459.903239 | 328.415763 | \-0.016794 | \-6.2176e-7 | 75431.07 | \[3\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Impact_of_Order_Latency_4_0.png) Live Order Latency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Impact%20of%20Order%20Latency.html#Live-Order-Latency "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230401, 20230406)\] ) asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | 1.536293 | 1.741565 | 0.007814 | 0.051916 | 4563.105627 | 336.150295 | 0.150518 | 0.000005 | 67694.55 | \[5\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_7_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Impact_of_Order_Latency_7_0.png) Order Latency from Amplified Feed Latency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Amplified-Feed-Latency "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Order entry latency is 4 times the feed latency and order response latency is 3 times the feed latency. \[6\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/amp\_feed\_latency\_20230401.npz',\ 'latency/amp\_feed\_latency\_20230402.npz',\ 'latency/amp\_feed\_latency\_20230403.npz',\ 'latency/amp\_feed\_latency\_20230404.npz',\ 'latency/amp\_feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[6\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.376802 | \-0.430111 | \-0.002163 | 0.053785 | 4366.301072 | 321.501683 | \-0.040224 | \-0.000001 | 75711.93 | \[7\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_10_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Impact_of_Order_Latency_10_0.png) --- # Impact of Order Latency — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Impact of Order Latency * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Impact%20of%20Order%20Latency.ipynb.txt) * * * Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Impact%20of%20Order%20Latency.html#Impact-of-Order-Latency "Link to this heading") ======================================================================================================================================================================== This example illustrates the impact of order latency on the performance of the strategy. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, order\_qty, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) Order Latency from Feed Latency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Feed-Latency "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Please see [the tutorial](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) on generating artificial order latency data from feed latency. \[2\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20230401.npz',\ 'latency/feed\_latency\_20230402.npz',\ 'latency/feed\_latency\_20230403.npz',\ 'latency/feed\_latency\_20230404.npz',\ 'latency/feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[2\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.197608 | \-0.224204 | \-0.001021 | 0.060794 | 4459.903239 | 328.415763 | \-0.016794 | \-6.2176e-7 | 75431.07 | \[3\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Impact_of_Order_Latency_4_0.png) Live Order Latency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Impact%20of%20Order%20Latency.html#Live-Order-Latency "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------- \[4\]: latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230401, 20230406)\] ) asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20230331\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | 1.536293 | 1.741565 | 0.007814 | 0.051916 | 4563.105627 | 336.150295 | 0.150518 | 0.000005 | 67694.55 | \[5\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_7_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Impact_of_Order_Latency_7_0.png) Order Latency from Amplified Feed Latency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Impact%20of%20Order%20Latency.html#Order-Latency-from-Amplified-Feed-Latency "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Order entry latency is 4 times the feed latency and order response latency is 3 times the feed latency. \[6\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20230401.npz',\ 'data/ethusdt\_20230402.npz',\ 'data/ethusdt\_20230403.npz',\ 'data/ethusdt\_20230404.npz',\ 'data/ethusdt\_20230405.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/amp\_feed\_latency\_20230401.npz',\ 'latency/amp\_feed\_latency\_20230402.npz',\ 'latency/amp\_feed\_latency\_20230403.npz',\ 'latency/amp\_feed\_latency\_20230404.npz',\ 'latency/amp\_feed\_latency\_20230405.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) gridtrading\_glft\_mm(hbt, 1, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=25\_000) stats.summary() \[6\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-04-01 00:00:00 | 2023-04-05 23:59:50 | \-0.376802 | \-0.430111 | \-0.002163 | 0.053785 | 4366.301072 | 321.501683 | \-0.040224 | \-0.000001 | 75711.93 | \[7\]: stats.plot() ![../_images/tutorials_Impact_of_Order_Latency_10_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Impact_of_Order_Latency_10_0.png) --- # Probability Queue Position Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Probability Queue Position Models * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Probability%20Queue%20Models.ipynb) * * * Probability Queue Position Models[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Probability%20Queue%20Models.html#Probability-Queue-Position-Models "Permalink to this heading") ============================================================================================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Probability%20Queue%20Models.html#Overview "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------- Here, we will demonstrate how queue position models affect order fill simulation and, ultimately, the strategy’s performance. It is essential for accurate backtesting to find the proper queue position modeling by comparing backtest and real trading results. In this context, we will illustrate comparisons by changing queue position models. By doing this, you can determine the appropriate queue position model that aligns with the backtesting and real trading results. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: from numba import njit from numba.typed import Dict from hftbacktest import BUY, SELL from hftbacktest import ( HftBacktest, Linear, Stat, GTX, COL\_SIDE, COL\_PRICE, IntpOrderLatency, SquareProbQueueModel, LogProbQueueModel2, PowerProbQueueModel3 ) import numpy as np @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, stat, gamma, order\_qty): arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) \# To properly calibrate A and K, a sufficient number of data points is required, here, with a minimum of three. \# If market trades only take place at the best bid and offer, an alternative method may be necessary \# to compute half spread and skew, since fitting a function might not be feasible due to insufficient \# data points. \# Alternatively, you can increase the time-step for measuring order arrivals, \# but this could result in a delayed response. half\_spread\_one \= False if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= hbt.position / order\_qty bid\_depth \= half\_spread + skew \* normalized\_position ask\_depth \= half\_spread \- skew \* normalized\_position bid\_price \= min(mid\_price\_tick \- bid\_depth, hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(mid\_price\_tick + ask\_depth, hbt.best\_ask\_tick) \* hbt.tick\_size grid\_interval \= max(np.round(half\_spread) \* hbt.tick\_size, hbt.tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. stat.record(hbt) \[2\]: def backtest(args): asset\_name, asset\_info, model \= args if model \== 'SquareProbQueueModel': queue\_model \= SquareProbQueueModel() elif model \== 'LogProbQueueModel2': queue\_model \= LogProbQueueModel2() elif model \== 'PowerProbQueueModel3': queue\_model \= PowerProbQueueModel3(3) else: raise ValueError latency\_data \= np.concatenate( \[np.load('../latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=queue\_model, asset\_type\=Linear, snapshot\='data/{}\_20230730\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gamma \= 0.00005 gridtrading\_glft\_mm(hbt, stat.recorder, gamma, order\_qty) np.savez( '{}\_stat\_000005\_{}'.format(asset\_name, model), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[3\]: %%capture from multiprocessing import Pool import json with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'SquareProbQueueModel') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'LogProbQueueModel2') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'PowerProbQueueModel3') for k, v in assets.items()\])) \[4\]: import pandas as pd from matplotlib import pyplot as plt def load\_equity(model): equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_000005\_{}.npz'.format(asset\_name, model)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_ \= equity.resample('5min').last() pnl \= equity\_.diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity\_ sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number \== 100: \# 2\_000 is capital for each trading asset. return (net\_equity / asset\_number) / 2\_000 np.seterr(divide\='ignore', invalid\='ignore') fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] for model in \['SquareProbQueueModel', 'LogProbQueueModel2', 'PowerProbQueueModel3'\]: net\_equity\_ \= load\_equity(model) pnl \= net\_equity\_.diff() sr \= pnl.mean() / pnl.std() \* np.sqrt(288) legend.append('100 assets, Daily SR={:.2f}, {}'.format(sr, model)) (net\_equity\_ \* 100).plot() plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[4\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Probability_Queue_Models_4_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Probability_Queue_Models_4_1.png) --- # Probability Queue Position Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Probability Queue Position Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Probability%20Queue%20Models.ipynb.txt) * * * Probability Queue Position Models[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Probability%20Queue%20Models.html#Probability-Queue-Position-Models "Link to this heading") =========================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Probability%20Queue%20Models.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- Here, we will demonstrate how queue position models affect order fill simulation and, ultimately, the strategy’s performance. It is essential for accurate backtesting to find the proper queue position modeling by comparing backtest and real trading results. In this context, we will illustrate comparisons by changing queue position models. By doing this, you can determine the appropriate queue position model that aligns with the backtesting and real trading results. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[2\]: def backtest(args): asset\_name, asset\_info, model \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) if model \== 'SquareProbQueueModel': asset.power\_prob\_queue\_model(2) elif model \== 'LogProbQueueModel2': asset.log\_prob\_queue\_model2() elif model \== 'PowerProbQueueModel3': asset.power\_prob\_queue\_model3(3) else: raise ValueError hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name)) \[3\]: %%capture from multiprocessing import Pool import json with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'SquareProbQueueModel') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'LogProbQueueModel2') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'PowerProbQueueModel3') for k, v in assets.items()\])) \[4\]: import polars as pl from matplotlib import pyplot as plt def compute\_net\_equity(model): equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number \== 100: \# 5\_000 is capital for each trading asset. return net\_equity.with\_columns( (pl.col('equity') / asset\_number / 5\_000).alias('equity') ) np.seterr(divide\='ignore', invalid\='ignore') fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] for model in \['SquareProbQueueModel', 'LogProbQueueModel2', 'PowerProbQueueModel3'\]: net\_equity\_ \= compute\_net\_equity(model) pnl \= net\_equity\_\['equity'\].diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('100 assets, Daily SR={:.2f}, {}'.format(sr, model)) plt.plot(net\_equity\_\['timestamp'\], net\_equity\_\['equity'\] \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[4\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Probability_Queue_Models_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Probability_Queue_Models_4_1.png) --- # Order Fill — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Order Fill * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/order_fill.rst.txt) * * * Order Fill[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#order-fill "Link to this heading") ================================================================================================================= Exchange Models[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#exchange-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------------- HftBacktest is a market-data replay-based backtesting tool, which means your order cannot make any changes to the simulated market, no market impact is considered. Therefore, one of the most important assumptions is that your order is small enough not to make any market impact. In the end, you must test it in a live market with real market participants and adjust your backtesting based on the discrepancies between the backtesting results and the live outcomes. Hftbacktest offers two types of exchange simulation. [NoPartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#order-fill-no-partial-fill-exchange) is the default exchange simulation where no partial fills occur. [PartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#order-fill-partial-fill-exchange) is the extended exchange simulation that accounts for partial fills in specific cases. Since the market-data replay-based backtesting cannot alter the market, some partial fill cases may still be unrealistic, such as taking market liquidity. This is because even if your order takes market liquidity, the replayed market data’s market depth and trades cannot change. It is essential to understand the underlying assumptions in each backtesting simulation. ### NoPartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#nopartialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#conditions-for-full-execution "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price * Your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price * Your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#liquidity-taking-order "Link to this heading") > Regardless of the quantity at the best, liquidity-taking orders will be fully executed at the best. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [NoPartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.NoPartialFillExchange.html) and [`no_partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange "hftbacktest.BacktestAsset.no_partial_fill_exchange") ### PartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#partialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#id2 "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price #### Conditions for Partial Execution[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#conditions-for-partial-execution "Link to this heading") Buy order in the order book * Filled by (remaining) sell trade quantity: your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Filled by (remaining) buy trade quantity: your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#id3 "Link to this heading") > Liquidity-taking orders will be executed based on the quantity of the order book, even though the best price and quantity do not change due to your execution. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [PartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.PartialFillExchange.html) and [`partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange "hftbacktest.BacktestAsset.partial_fill_exchange") Queue Models[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#queue-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------- Knowing your order’s queue position is important to achieve accurate order fill simulation in backtesting depending on the liquidity of an order book and trading activities. If an exchange doesn’t provide Market-By-Order, you have to guess it by modeling. HftBacktest currently only supports Market-By-Price that is most crypto exchanges provide and it provides the following queue position models for order fill simulation. Please refer to the details at Models . ![_images/liquidity-and-trade-activities.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/liquidity-and-trade-activities.png) ### RiskAverseQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#riskaversequeuemodel "Link to this heading") This model is the most conservative model in terms of the chance of fill in the queue. The decrease in quantity by cancellation or modification in the order book happens only at the tail of the queue so your order queue position doesn’t change. The order queue position will be advanced only if a trade happens at the price. You can find details below. * [RiskAdverseQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.RiskAdverseQueueModel.html) and [`risk_adverse_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model "hftbacktest.BacktestAsset.risk_adverse_queue_model") ### ProbQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#probqueuemodel "Link to this heading") Based on a probability model according to your current queue position, the decrease in quantity happens at both before and after the queue position. So your queue position is also advanced according to the probability. This model is implemented as described in * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) * [https://rigtorp.se/2013/06/08/estimating-order-queue-position.html](https://rigtorp.se/2013/06/08/estimating-order-queue-position.html) You can find details below. * [ProbQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ProbQueueModel.html) * [PowerProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc.html) and [`power_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model "hftbacktest.BacktestAsset.power_prob_queue_model") * [PowerProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc2.html) and [`power_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2 "hftbacktest.BacktestAsset.power_prob_queue_model2") * [PowerProbQueueFunc3](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc3.html) and [`power_prob_queue_model3`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3 "hftbacktest.BacktestAsset.power_prob_queue_model3") * [LogProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc.html) and [`log_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model "hftbacktest.BacktestAsset.log_prob_queue_model") * [LogProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc2.html) and [`log_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2 "hftbacktest.BacktestAsset.log_prob_queue_model2") By default, three variations are provided. These three models have different probability profiles. ![_images/probqueuemodel.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/probqueuemodel.png) The function f = log(1 + x) exhibits a different probability profile depending on the total quantity at the price level, unlike power functions. ![_images/probqueuemodel_log.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/probqueuemodel_log.png) ![_images/probqueuemodel2.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/probqueuemodel2.png) ![_images/probqueuemodel3.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/probqueuemodel3.png) When you set the function f, it should be as follows. * The probability at 0 should be 0 because if the order is at the head of the queue, all decreases should happen after the order. * The probability at 1 should be 1 because if the order is at the tail of the queue, all decreases should happen before the order. You can see the comparison of the models [here](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Probability%20Queue%20Models.html) . ### Implement a custom queue model[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#implement-a-custom-queue-model "Link to this heading") You need to implement the following traits in Rust based on your usage requirements. * [QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.QueueModel.html) * [L3QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.L3QueueModel.html) Please refer to [the queue model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/queue.rs) . References[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/order_fill.html#references "Link to this heading") ----------------------------------------------------------------------------------------------------------------- This is initially implemented as described in the following articles. * [http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf](http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf) * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) --- # Getting Started — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Getting Started * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Getting%20Started.ipynb) * * * Getting Started[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Getting-Started "Permalink to this heading") ============================================================================================================================================== Printing the best bid and the best ask[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Printing-the-best-bid-and-the-best-ask "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: from numba import njit \# numba.njit is strongly recommended for fast backtesting. @njit def print\_bbo(hbt): \# Iterating until hftbacktest reaches the end of data. while hbt.run: \# Elapses 60-sec every iteration. \# Time unit is the same as data's timestamp's unit. \# timestamp of the sample data is in microseconds. if not hbt.elapse(60 \* 1e6): \# hftbacktest encounters the end of data while elapsing. return False \# Prints the best bid and the best offer. print( 'current\_timestamp:', hbt.current\_timestamp, ', best\_bid:', round(hbt.best\_bid, 3), ', best\_ask:', round(hbt.best\_ask, 3) ) return True \[2\]: from hftbacktest import HftBacktest, FeedLatency, Linear hbt \= HftBacktest( 'btcusdt\_20230405.npz', tick\_size\=0.1, \# Tick size of a target trading asset lot\_size\=0.001, \# Lot size of a target trading asset, minimum trading unit. maker\_fee\=0.0002, \# 0.02%, Maker fee, rebates if it is negative. taker\_fee\=0.0007, \# 0.07%, Taker fee. order\_latency\=FeedLatency(), \# Latency model: ConstantLatency, FeedLatency. asset\_type\=Linear, \# Asset type: Linear, Inverse. snapshot\='btcusdt\_20230404\_eod.npz' ) Load btcusdt\_20230405.npz You can see the best bid and the best ask every 60-sec. \[3\]: print\_bbo(hbt) current\_timestamp: 1680652860032116 , best\_bid: 28150.7 , best\_ask: 28150.8 current\_timestamp: 1680652920032116 , best\_bid: 28144.1 , best\_ask: 28144.2 current\_timestamp: 1680652980032116 , best\_bid: 28149.9 , best\_ask: 28150.0 current\_timestamp: 1680653040032116 , best\_bid: 28145.7 , best\_ask: 28145.8 current\_timestamp: 1680653100032116 , best\_bid: 28140.5 , best\_ask: 28140.6 current\_timestamp: 1680653160032116 , best\_bid: 28143.8 , best\_ask: 28143.9 \[3\]: False Feeding the data[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Feeding-the-data "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------ When you possess adequate memory, preloading the data into memory and providing it as input will be more efficient than lazy-loading during repeated backtesting. HftBacktest is compatible with either `numpy` arrays or `pandas` DataFrames. \[4\]: import numpy as np btcusdt\_20230405 \= np.load('btcusdt\_20230405.npz')\['data'\] btcusdt\_20230404\_eod \= np.load('btcusdt\_20230404\_eod.npz')\['data'\] hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) You can also provide the list of data. \[5\]: hbt \= HftBacktest( \[\ btcusdt\_20230405\ \], tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) Due to the vast size of tick-by-tick market depth and trade data, loading the entire dataset into memory can be challenging, particularly when backtesting across multiple days. HftBacktest offers lazy loading support and is compatible with `npy`, `npz` (data should be stored under the `data` key), and pickled `pandas` DataFrames. \[6\]: hbt \= HftBacktest( \[\ 'btcusdt\_20230405.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\='btcusdt\_20230404\_eod.npz' ) Load btcusdt\_20230405.npz Getting the market depth[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Getting-the-market-depth "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def print\_3depth(hbt): while hbt.run: if not hbt.elapse(60 \* 1e6): return False \# a key of bid\_depth or ask\_depth is price in tick format. \# (integer) price\_tick = price / tick\_size print('current\_timestamp:', hbt.current\_timestamp) i \= 0 for tick\_price in range(hbt.best\_ask\_tick, hbt.high\_ask\_tick + 1): if tick\_price in hbt.ask\_depth: print( 'ask: ', hbt.ask\_depth\[tick\_price\], '@', round(tick\_price \* hbt.tick\_size, 3) ) i += 1 if i \== 3: break i \= 0 for tick\_price in range(hbt.best\_bid\_tick, hbt.low\_bid\_tick \- 1, \-1): if tick\_price in hbt.bid\_depth: print( 'bid: ', hbt.bid\_depth\[tick\_price\], '@', round(tick\_price \* hbt.tick\_size, 3) ) i += 1 if i \== 3: break return True \[ \]: \[8\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) print\_3depth(hbt) current\_timestamp: 1680652860032116 ask: 9.228 @ 28150.8 ask: 0.387 @ 28150.9 ask: 3.996 @ 28151.0 bid: 3.135 @ 28150.7 bid: 0.002 @ 28150.6 bid: 0.813 @ 28150.5 current\_timestamp: 1680652920032116 ask: 1.224 @ 28144.2 ask: 0.223 @ 28144.3 ask: 0.001 @ 28144.5 bid: 10.529 @ 28144.1 bid: 0.168 @ 28144.0 bid: 0.29 @ 28143.9 current\_timestamp: 1680652980032116 ask: 3.397 @ 28150.0 ask: 1.282 @ 28150.1 ask: 0.003 @ 28150.4 bid: 7.951 @ 28149.9 bid: 0.02 @ 28149.8 bid: 0.02 @ 28149.7 current\_timestamp: 1680653040032116 ask: 3.905 @ 28145.8 ask: 1.695 @ 28145.9 ask: 0.003 @ 28146.0 bid: 5.793 @ 28145.7 bid: 0.059 @ 28145.6 bid: 0.044 @ 28145.5 current\_timestamp: 1680653100032116 ask: 6.8 @ 28140.6 ask: 0.001 @ 28140.7 ask: 0.004 @ 28141.1 bid: 2.416 @ 28140.5 bid: 0.004 @ 28140.4 bid: 0.012 @ 28140.3 current\_timestamp: 1680653160032116 ask: 3.666 @ 28143.9 ask: 1.422 @ 28144.0 ask: 1.455 @ 28144.1 bid: 3.189 @ 28143.8 bid: 5.136 @ 28143.7 bid: 0.012 @ 28143.5 \[8\]: False Submitting an order[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Submitting-an-order "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------ \[9\]: from hftbacktest import GTC, NONE, NEW, FILLED, CANCELED, EXPIRED @njit def print\_orders(hbt): \# You can access open orders and also closed orders via hbt.orders. \# hbt.orders is a Numba dictionary and its key is order\_id(int). for order\_id, order in hbt.orders.items(): order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' \# Exchange hasn't received an order yet. elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order\_id, ', order\_price:', order.price, ', order\_qty:', order.qty, ', order\_status:', order\_status, ', order\_req:', order\_req ) @njit def submit\_order(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(30 \* 1e6): return False \# Prints open orders. print\_orders(hbt) if not is\_order\_submitted: \# Submits a buy order at 100 tick below the best bid. order\_id \= 1 order\_price \= hbt.best\_bid \- 100 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC \# Good 'till cancel hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) is\_order\_submitted \= True return True \[10\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) submit\_order(hbt) current\_timestamp: 1680652860032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE current\_timestamp: 1680652890032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680652920032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680652950032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680652980032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653010032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653040032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653070032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653100032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653130032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653160032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680653190032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE \[10\]: False Clearing inactive orders (FILLED, CANCELED, EXPIRED)[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Clearing-inactive-orders-(FILLED,-CANCELED,-EXPIRED) "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[11\]: from hftbacktest import GTC @njit def clear\_inactive\_orders(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(30 \* 1e6): return False print\_orders(hbt) \# Removes inactive(FILLED, CANCELED, EXPIRED) orders from hbt.orders. hbt.clear\_inactive\_orders() if not is\_order\_submitted: order\_id \= 1 order\_price \= hbt.best\_bid \- 100 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) is\_order\_submitted \= True return True \[12\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) clear\_inactive\_orders(hbt) current\_timestamp: 1680652860032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE current\_timestamp: 1680652890032116 , order\_id: 1 , order\_price: 28146.300000000003 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE \[12\]: False Watching a order status - pending due to order latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Watching-a-order-status---pending-due-to-order-latency "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[13\]: from hftbacktest import GTC @njit def watch\_pending(hbt): is\_order\_submitted \= False while hbt.run: \# Elapses 0.01-sec every iteration. if not hbt.elapse(0.01 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() if not is\_order\_submitted: order\_id \= 1 order\_price \= hbt.best\_bid \- 100 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) is\_order\_submitted \= True \# Prevents too many prints if hbt.orders\[order\_id\].status \== NEW: return False return True \[14\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) watch\_pending(hbt) current\_timestamp: 1680652800052116 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: NONE , order\_req: NEW current\_timestamp: 1680652800062116 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: NONE , order\_req: NEW current\_timestamp: 1680652800072116 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE \[14\]: False Waiting for an order response[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Waiting-for-an-order-response "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[15\]: from hftbacktest import GTC @njit def wait\_for\_order\_response(hbt): order\_id \= 0 is\_order\_submitted \= False while hbt.run: if not hbt.elapse(0.01 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() \# Prevent too many prints if order\_id in hbt.orders: if hbt.orders\[order\_id\].status \== NEW: return False if not is\_order\_submitted: order\_id \= 1 order\_price \= hbt.best\_bid order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) \# Wait for the order response for a given order id. print('an order is submitted at', hbt.current\_timestamp) hbt.wait\_order\_response(order\_id) print('an order response is received at', hbt.current\_timestamp) is\_order\_submitted \= True return True \[16\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) wait\_for\_order\_response(hbt) an order is submitted at 1680652800042116 an order response is received at 1680652800070493 current\_timestamp: 1680652800080493 , order\_id: 1 , order\_price: 28155.100000000002 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE \[16\]: False Printing position, balance, fee, and equity[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Printing-position,-balance,-fee,-and-equity "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ \[17\]: @njit def position(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(60 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() \# Prints position, balance, fee, and equity print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position, ', balance:', hbt.balance, ', fee:', hbt.fee, ', equity:', hbt.equity ) if not is\_order\_submitted: order\_id \= 1 order\_price \= hbt.best\_bid order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) hbt.wait\_order\_response(order\_id) is\_order\_submitted \= True return True \[18\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) position(hbt) current\_timestamp: 1680652860032116 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680652920095398 , order\_id: 1 , order\_price: 28150.7 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680652920095398 , position: 1.0 , balance: -28150.7 , fee: 5.630140000000001 , equity: -12.180139999999273 current\_timestamp: 1680652980095398 , position: 1.0 , balance: -28150.7 , fee: 5.630140000000001 , equity: -6.380140000000001 current\_timestamp: 1680653040095398 , position: 1.0 , balance: -28150.7 , fee: 5.630140000000001 , equity: -10.580140000000728 current\_timestamp: 1680653100095398 , position: 1.0 , balance: -28150.7 , fee: 5.630140000000001 , equity: -15.780139999997818 current\_timestamp: 1680653160095398 , position: 1.0 , balance: -28150.7 , fee: 5.630140000000001 , equity: -12.480139999998546 \[18\]: False Canceling an open order[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Canceling-an-open-order "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------- \[19\]: @njit def submit\_and\_cancel\_order(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(0.1 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() \# Cancels if there is an open order for order\_id, order in hbt.orders.items(): \# an order is only cancellable if order status is NEW. \# cancel request is negated if the order is already filled or filled before cancel request is processed. if order.cancellable: hbt.cancel(order\_id) \# You can see status still NEW and see req CANCEL. print\_orders(hbt) \# cancels request also has order entry/response latencies the same as submitting. hbt.wait\_order\_response(order\_id) if not is\_order\_submitted: order\_id \= 1 order\_price \= hbt.best\_bid \- 100 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, time\_in\_force) hbt.wait\_order\_response(order\_id) is\_order\_submitted \= True else: if len(hbt.orders) \== 0: return False return True \[20\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) submit\_and\_cancel\_order(hbt) current\_timestamp: 1680652800254816 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: NEW , order\_req: NONE current\_timestamp: 1680652800254816 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: NEW , order\_req: CANCEL current\_timestamp: 1680652800973764 , order\_id: 1 , order\_price: 28145.100000000002 , order\_qty: 1.0 , order\_status: CANCELED , order\_req: NONE \[20\]: False Market order[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Market-order "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------- \[21\]: @njit def print\_orders\_exec\_price(hbt): for order\_id, order in hbt.orders.items(): order\_status \= '' if order.status \== NONE: order\_status \= 'NONE' elif order.status \== NEW: order\_status \= 'NEW' elif order.status \== FILLED: order\_status \= 'FILLED' elif order.status \== CANCELED: order\_status \= 'CANCELED' elif order.status \== EXPIRED: order\_status \= 'EXPIRED' order\_req \= '' if order.req \== NONE: order\_req \= 'NONE' elif order.req \== NEW: order\_req \= 'NEW' elif order.req \== CANCELED: order\_req \= 'CANCEL' print( 'current\_timestamp:', hbt.current\_timestamp, ', order\_id:', order\_id, ', order\_price:', order.price, ', order\_qty:', order.qty, ', order\_status:', order\_status, ', exec\_price:', order.exec\_price ) @njit def market\_order(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(60 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position, ', balance:', hbt.balance, ', fee:', hbt.fee, ', equity:', hbt.equity ) if not is\_order\_submitted: order\_id \= 1 \# Sets a deep price in the opposite side to take liquidity. order\_price \= hbt.best\_bid \- 50 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, time\_in\_force) hbt.wait\_order\_response(order\_id) \# You can see the order immediately filled. \# Also you can see the order executed at the best bid which is different from what it was submitted at. print('best\_bid:', hbt.best\_bid) print\_orders\_exec\_price(hbt) is\_order\_submitted \= True return True \[22\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) market\_order(hbt) current\_timestamp: 1680652860032116 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 best\_bid: 28150.7 current\_timestamp: 1680652860095398 , order\_id: 1 , order\_price: 28145.7 , order\_qty: 1.0 , order\_status: FILLED , exec\_price: 28150.7 current\_timestamp: 1680652920095398 , order\_id: 1 , order\_price: 28145.7 , order\_qty: 1.0 , order\_status: FILLED , order\_req: NONE current\_timestamp: 1680652920095398 , position: -1.0 , balance: 28150.7 , fee: 19.70549 , equity: -13.155490000000729 current\_timestamp: 1680652980095398 , position: -1.0 , balance: 28150.7 , fee: 19.70549 , equity: -18.95549 current\_timestamp: 1680653040095398 , position: -1.0 , balance: 28150.7 , fee: 19.70549 , equity: -14.755489999999273 current\_timestamp: 1680653100095398 , position: -1.0 , balance: 28150.7 , fee: 19.70549 , equity: -9.555490000002184 current\_timestamp: 1680653160095398 , position: -1.0 , balance: 28150.7 , fee: 19.70549 , equity: -12.855490000001456 \[22\]: False GTX, Post-Only order[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#GTX,-Post-Only-order "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------- \[23\]: from hftbacktest import GTX @njit def submit\_gtx(hbt): is\_order\_submitted \= False while hbt.run: if not hbt.elapse(60 \* 1e6): return False print\_orders(hbt) hbt.clear\_inactive\_orders() print( 'current\_timestamp:', hbt.current\_timestamp, ', position:', hbt.position, ', balance:', hbt.balance, ', fee:', hbt.fee, ', equity:', hbt.equity ) if not is\_order\_submitted: order\_id \= 1 \# Sets a deep price in the opposite side and it will be rejected by GTX. order\_price \= hbt.best\_bid \- 100 \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTX hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, time\_in\_force) hbt.wait\_order\_response(order\_id) is\_order\_submitted \= True return True \[24\]: hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) submit\_gtx(hbt) current\_timestamp: 1680652860032116 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680652920095398 , order\_id: 1 , order\_price: 28140.7 , order\_qty: 1.0 , order\_status: EXPIRED , order\_req: NONE current\_timestamp: 1680652920095398 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680652980095398 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680653040095398 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680653100095398 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 current\_timestamp: 1680653160095398 , position: 0.0 , balance: 0.0 , fee: 0.0 , equity: 0.0 \[24\]: False Plotting BBO[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Plotting-BBO "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------- \[25\]: @njit def plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask): while hbt.run: if not hbt.elapse(1 \* 1e6): return False \# Records data points local\_timestamp.append(hbt.current\_timestamp) best\_bid.append(hbt.best\_bid) best\_ask.append(hbt.best\_ask) return True \[26\]: \# Uses Numba list for njit. from numba.typed import List from numba import float64 import pandas as pd hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) local\_timestamp \= List.empty\_list(float64, allocated\=10000) best\_bid \= List.empty\_list(float64, allocated\=10000) best\_ask \= List.empty\_list(float64, allocated\=10000) plot\_bbo(hbt, local\_timestamp, best\_bid, best\_ask) local\_timestamp \= pd.to\_datetime(local\_timestamp, unit\='us', utc\=True) best\_bid \= pd.Series(best\_bid, index\=local\_timestamp) best\_ask \= pd.Series(best\_ask, index\=local\_timestamp) best\_bid.plot() best\_ask.plot() \[26\]: ![../_images/tutorials_Getting_Started_43_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Getting_Started_43_1.png) Printing stats[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Getting%20Started.html#Printing-stats "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------- \[27\]: @njit def submit\_order\_stats(hbt, recorder): buy\_order\_id \= 1 sell\_order\_id \= 2 half\_spread \= 1 \* hbt.tick\_size while hbt.run: if not hbt.elapse(1 \* 1e6): return False hbt.clear\_inactive\_orders() mid \= (hbt.best\_bid + hbt.best\_ask) / 2.0 if buy\_order\_id not in hbt.orders: order\_price \= round((mid \- half\_spread) / hbt.tick\_size) \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_buy\_order(buy\_order\_id, order\_price, order\_qty, time\_in\_force) if sell\_order\_id not in hbt.orders: order\_price \= round((mid + half\_spread) / hbt.tick\_size) \* hbt.tick\_size order\_qty \= 1 time\_in\_force \= GTC hbt.submit\_sell\_order(sell\_order\_id, order\_price, order\_qty, time\_in\_force) recorder.record(hbt) return True \[28\]: from hftbacktest import Stat hbt \= HftBacktest( btcusdt\_20230405, tick\_size\=0.1, lot\_size\=0.001, maker\_fee\=0.0002, taker\_fee\=0.0007, order\_latency\=FeedLatency(), asset\_type\=Linear, snapshot\=btcusdt\_20230404\_eod ) stat \= Stat(hbt) submit\_order\_stats(hbt, stat.recorder) \# Default resample is 5min. stat.summary(capital\=200000, resample\='1s') \=========== Summary =========== Sharpe ratio: -355.7 Sortino ratio: -210.0 Risk return ratio: -52717.6 Annualised return: -3141.57 % Max. draw down: 0.06 % The number of trades per day: 9 Avg. daily trading volume: 9 Avg. daily trading amount: 255269 Max leverage: 1.27 Median leverage: 0.14 ![../_images/tutorials_Getting_Started_46_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Getting_Started_46_1.png) --- # High-Frequency Grid Trading — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * High-Frequency Grid Trading * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/High-Frequency%20Grid%20Trading.ipynb.txt) * * * High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading "Link to this heading") ================================================================================================================================================================================== **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Plain High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#Plain-High-Frequency-Grid-Trading "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is a high-frequency version of Grid Trading that keeps posting orders on grids centered around the mid-price, maintaining a fixed interval and a set number of grids. \[1\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT @njit def gridtrading(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# Aligns the prices to the grid. bid\_price \= np.floor((mid\_price \- half\_spread) / grid\_interval) \* grid\_interval ask\_price \= np.ceil((mid\_price + half\_spread) / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True For generating order latency from the feed data file, which uses feed latency as order latency, please see [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) . \[2\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) \[3\]: %%time gridtrading(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 6min 5s, sys: 9.08 s, total: 6min 15s Wall time: 6min 16s \[4\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.265693 | 25.144025 | 0.082691 | 0.021906 | 9489.819672 | 127.266294 | 3.774836 | 0.00013 | 9140.288 | \[5\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_7_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_High-Frequency_Grid_Trading_7_0.png) High-Frequency Grid Trading with Skewing[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading-with-Skewing "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ By incorporating position-based skewing, the strategy’s risk-adjusted returns can be improved. \[6\]: @njit def gridtrading(hbt, recorder, skew): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* tick\_size \* position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True ### Weak skew[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#Weak-skew "Link to this heading") \[7\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 1 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[7\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.363916 | 25.321583 | 0.060482 | 0.014831 | 10563.644529 | 141.707178 | 4.077966 | 0.000085 | 9409.12 | \[8\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_12_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_High-Frequency_Grid_Trading_12_0.png) ### Strong skew[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#Strong-skew "Link to this heading") Under strong skew, the position is more limited compared to the weak skew case. You may also observe a spike in equity when the market moves sharply. However, in reality, this might not be realized due to order latency. Later, we will explore the impact of order latency and highlight the importance of using actual historical order latency data. \[9\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 10 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[9\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 27.282302 | 47.25453 | 0.042574 | 0.005391 | 11838.874048 | 158.842253 | 7.897853 | 0.000054 | 8270.01 | \[10\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_15_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_High-Frequency_Grid_Trading_15_0.png) Multiple Assets[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/High-Frequency%20Grid%20Trading.html#Multiple-Assets "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------- You might need to find the proper parameters for each asset to achieve better performance. As an example, here it uses single parameters set to demonstrate how the performance of a combination of multiple assets will be. \[11\]: @njit def gridtrading(hbt, recorder, half\_spread, grid\_interval, skew, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= grid\_num \* order\_qty \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Ensures the grid interval aligns with the tick size, with the minimum set to the tick size. grid\_interval \= max(np.round(grid\_interval / tick\_size) \* tick\_size, tick\_size) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[12\]: from hftbacktest import BUY\_EVENT, SELL\_EVENT latency\_data \= np.concatenate( \[np.load('latency/live\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .log\_prob\_queue\_model2() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] half\_spread \= mid\_price \* 0.0008 grid\_interval \= mid\_price \* 0.0008 skew \= mid\_price \* 0.000025 recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, skew, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_{}.npz'.format(asset\_name)) \[13\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[14\]: import polars as pl from hftbacktest.stats import LinearAssetRecord equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[15\]: from matplotlib import pyplot as plt fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 10 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[15\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_21_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_High-Frequency_Grid_Trading_21_1.png) --- # Constants — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Constants * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/reference/constants.rst.txt) * * * Constants[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#constants "Link to this heading") ======================================================================================================================== EXCH\_EVENT _\= 2147483648_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the exchange processor at the exchange timestamp. LOCAL\_EVENT _\= 1073741824_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the local processor at the local timestamp. BUY\_EVENT _\= 536870912_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.BUY_EVENT "Link to this definition") Indicates a buy, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means a bid-side event, while when combined with a trade event, it means that the trade initiator is a buyer. SELL\_EVENT _\= 268435456_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.SELL_EVENT "Link to this definition") Indicates a sell, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means an ask-side event, while when combined with a trade event, it means that the trade initiator is a seller. MARKET _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.MARKET "Link to this definition") MARKET LIMIT _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.LIMIT "Link to this definition") LIMIT BUY _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.BUY "Link to this definition") In the market depth event, this indicates the bid side; in the market trade event, it indicates that the trade initiator is a buyer. SELL _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.SELL "Link to this definition") In the market depth event, this indicates the ask side; in the market trade event, it indicates that the trade initiator is a seller. NONE _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NONE "Link to this definition") NONE NEW _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NEW "Link to this definition") NEW EXPIRED _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.EXPIRED "Link to this definition") EXPIRED FILLED _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FILLED "Link to this definition") FILLED PARTIALLY\_FILLED _\= 5_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "Link to this definition") PARTIALLY\_FILLED CANCELED _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.CANCELED "Link to this definition") CANCELED REJECTED _\= 6_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.REJECTED "Link to this definition") REJECTED GTC _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTC "Link to this definition") Good ‘till cancel GTX _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTX "Link to this definition") Post only FOK _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FOK "Link to this definition") Fill or kill IOC _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.IOC "Link to this definition") Immediate or cancel ALL\_ASSETS _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.ALL_ASSETS "Link to this definition") Indicates all assets. DEPTH\_EVENT _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT "Link to this definition") Indicates that the market depth is changed. TRADE\_EVENT _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.TRADE_EVENT "Link to this definition") Indicates that a trade occurs in the market. DEPTH\_CLEAR\_EVENT _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT "Link to this definition") Indicates that the market depth is cleared. DEPTH\_SNAPSHOT\_EVENT _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT "Link to this definition") Indicates that the market depth snapshot is received. UNTIL\_END\_OF\_DATA _\= 9223372036854775807_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA "Link to this definition") Indicates that one should continue until the end of the data. --- # High-Frequency Grid Trading — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * High-Frequency Grid Trading * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/High-Frequency%20Grid%20Trading.ipynb.txt) * * * High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading "Link to this heading") ================================================================================================================================================================================== **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Plain High-Frequency Grid Trading[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#Plain-High-Frequency-Grid-Trading "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is a high-frequency version of Grid Trading that keeps posting orders on grids centered around the mid-price, maintaining a fixed interval and a set number of grids. \[1\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT @njit def gridtrading(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# Aligns the prices to the grid. bid\_price \= np.floor((mid\_price \- half\_spread) / grid\_interval) \* grid\_interval ask\_price \= np.ceil((mid\_price + half\_spread) / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True For generating order latency from the feed data file, which uses feed latency as order latency, please see [Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html) . \[2\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) \[3\]: %%time gridtrading(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 6min 5s, sys: 9.08 s, total: 6min 15s Wall time: 6min 16s \[4\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.265693 | 25.144025 | 0.082691 | 0.021906 | 9489.819672 | 127.266294 | 3.774836 | 0.00013 | 9140.288 | \[5\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_7_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_7_0.png) High-Frequency Grid Trading with Skewing[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#High-Frequency-Grid-Trading-with-Skewing "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ By incorporating position-based skewing, the strategy’s risk-adjusted returns can be improved. \[6\]: @njit def gridtrading(hbt, recorder, skew): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= 5 grid\_interval \= tick\_size \* 10 half\_spread \= tick\_size \* 20 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 0.1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* tick\_size \* position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True ### Weak skew[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#Weak-skew "Link to this heading") \[7\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 1 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[7\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 18.363916 | 25.321583 | 0.060482 | 0.014831 | 10563.644529 | 141.707178 | 4.077966 | 0.000085 | 9409.12 | \[8\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_12_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_12_0.png) ### Strong skew[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#Strong-skew "Link to this heading") Under strong skew, the position is more limited compared to the weak skew case. You may also observe a spike in equity when the market moves sharply. However, in reality, this might not be realized due to order latency. Later, we will explore the impact of order latency and highlight the importance of using actual historical order latency data. \[9\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) skew \= 10 recorder \= Recorder(1, 5\_000\_000) gridtrading(hbt, recorder.recorder, skew) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=10\_000) stats.summary() \[9\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 27.282302 | 47.25453 | 0.042574 | 0.005391 | 11838.874048 | 158.842253 | 7.897853 | 0.000054 | 8270.01 | \[10\]: stats.plot() ![../_images/tutorials_High-Frequency_Grid_Trading_15_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_15_0.png) Multiple Assets[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/High-Frequency%20Grid%20Trading.html#Multiple-Assets "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------- You might need to find the proper parameters for each asset to achieve better performance. As an example, here it uses single parameters set to demonstrate how the performance of a combination of multiple assets will be. \[11\]: @njit def gridtrading(hbt, recorder, half\_spread, grid\_interval, skew, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 20 max\_position \= grid\_num \* order\_qty \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= mid\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Ensures the grid interval aligns with the tick size, with the minimum set to the tick size. grid\_interval \= max(np.round(grid\_interval / tick\_size) \* tick\_size, tick\_size) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[12\]: from hftbacktest import BUY\_EVENT, SELL\_EVENT latency\_data \= np.concatenate( \[np.load('latency/live\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .log\_prob\_queue\_model2() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0) .roi\_ub(mid\_price \* 5) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] half\_spread \= mid\_price \* 0.0008 grid\_interval \= mid\_price \* 0.0008 skew \= mid\_price \* 0.000025 recorder \= Recorder(1, 50\_000\_000) gridtrading(hbt, recorder.recorder, half\_spread, grid\_interval, skew, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_{}.npz'.format(asset\_name)) \[13\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[14\]: import polars as pl from hftbacktest.stats import LinearAssetRecord equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity \[15\]: from matplotlib import pyplot as plt fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 10 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[15\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_High-Frequency_Grid_Trading_21_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_High-Frequency_Grid_Trading_21_1.png) --- # Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/data.rst.txt) * * * Data[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/data.html#data "Link to this heading") =============================================================================================== Please see [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) or [Data Preparation](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Data%20Preparation.html) regarding collecting and converting the feed data. Format[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/data.html#format "Link to this heading") --------------------------------------------------------------------------------------------------- hftbacktest can digest a numpy structured array. The data has 8 fields in the following order. You can also find details in [Event](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/struct.Event.html) . * ev (u64): You can find the possible flag combinations in [Constants](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/index.html#constants) . * exch\_ts (i64): Exchange timestamp, which is the time at which the event occurs on the exchange. * local\_ts (i64): Local timestamp, which is the time at which the event is received by the local. * px (f64): Price * qty (f64): Quantity * order\_id (u64): Order ID is only for the L3 Market-By-Order feed. * ival (i64): Reserved for an additional i64 value * faval (f64): Reserved for an additional f64 value **Raw data** > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22171.70 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22187.30 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.30 | 0.270 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.70 | 0.423 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22195.20 | 2.075 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22209.60 | 4.506 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205116000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | Validation[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/data.html#validation "Link to this heading") ----------------------------------------------------------------------------------------------------------- 1. All timestamps must be in the correct order, chronological order. There can be cases where an event happens before another at the exchange, resulting in an earlier exchange timestamp, but it is received locally after the other event. This reverses the chronological order of exchange and local timestamps. To handle this situation, hftbacktest uses the [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. Events flagged with [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") should be in chronological order according to the exchange timestamp, while events flagged with [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") should be in chronological order according to the local timestamp. 2. The exchange timestamp must be earlier than the local timestamp; the feed latency must be positive. Due to potential errors in time synchronization between two sites, the local timestamp may be earlier than the exchange timestamp, resulting in negative latency. The best way to address this is to improve time synchronization using PTP (Precision Time Protocol), which minimizes the possibility of negative latency. However, by adding a base latency or offsetting the size of the negative latency, you can ensure that the data remains valid with only positive latencies, where the local timestamp is always later than the exchange timestamp of the event. See the following example. The exchange timestamp of the depth feed is advanced to the prior trade feed even though the depth feed is received after the trade feed. > 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} This should be converted into the following form. HftBacktest provides [`correct_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#hftbacktest.data.correct_event_order "hftbacktest.data.correct_event_order") method to automatically correct this issue. [`validate_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/data_validation.html#hftbacktest.data.validate_event_order "hftbacktest.data.validate_event_order") helps to check if this issue exists. > EXCH\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > LOCAL\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212385000 | 22177.90 | 0.300 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212480000 | 22177.90 | 0.119 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212621000 | 22177.90 | 0.005 | 0 | 0 | 0.0 | --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Index * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#M) | [**N**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#N) | [**O**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#P) | [**Q**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#Q) | [**R**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#T) | [**U**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#U) | [**V**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/py-v2.2.0/genindex.html#W) A - | | | | --- | --- | | * [ALL\_ASSETS (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.ALL_ASSETS)

* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency)

* [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size) | * [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.databento)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_depth() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_depth)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency) | * [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.FILLED)

* [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.FOK) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.databento
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [l3\_fifo\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.l3_fifo_queue_model)

* [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [latency\_offset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.latency_offset)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.LIMIT)

* [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset) | * [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord)

* [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Metric)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.databento](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [parallel\_load() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.parallel_load)

* [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.price_tick) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD) | * [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade)

* [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Stats) | * [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # Order Latency Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Order Latency Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Order%20Latency%20Data.ipynb.txt) * * * Order Latency Data[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Order%20Latency%20Data.html#Order-Latency-Data "Link to this heading") ======================================================================================================================================================= To obtain more realistic backtesting results, accounting for latencies is crucial. Therefore, it’s important to collect both feed data and order data with timestamps to measure your order latency. The best approach is to gather your own order latencies. You can collect order latency based on your live trading or by regularly submitting orders at a price that cannot be filled and then canceling them for recording purposes. However, if you don’t have access to them or want to establish a target, you will need to artificially generate order latency. You can model this latency based on factors such as feed latency, trade volume, and the number of events. In this guide, we will demonstrate a simple method to generate order latency from feed latency using a multiplier and offset for adjustment. First, loads the feed data. \[1\]: import numpy as np data \= np.load('btcusdt\_20200201.npz')\['data'\] data \[1\]: array(\[(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\ ...,\ (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\ (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\ (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)\], dtype=\[('ev', '
* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset)

* [balance (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.balance)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency)

* [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size) | * [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.databento)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_depth() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_depth)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [fee (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.fee)

* [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency) | * [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.FILLED)

* [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.FOK) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.databento
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [l3\_fifo\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.l3_fifo_queue_model)

* [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [latency\_offset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.latency_offset)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.LIMIT)

* [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset) | * [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord)

* [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Metric)

* [modify() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.modify)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.modify)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.databento](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [num\_trades (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.num_trades)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [parallel\_load() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.parallel_load)

* [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.position)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.price_tick) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD) | * [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade)

* [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [StateValues (class in hftbacktest.state)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues) | * [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Stats)

* [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.trading_value)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [trading\_volume (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.state.StateValues.trading_volume)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # Initialization — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Initialization * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/reference/initialization.rst.txt) * * * Initialization[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#initialization "Link to this heading") ======================================================================================================================================= _class_ BacktestAsset[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest.html#BacktestAsset) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset "Link to this definition") data(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest.html#BacktestAsset.data) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/initialization.html#hftbacktest.BacktestAsset.data "Link to this definition") Sets the feed data. Parameters: **data** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") _|_ [_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _\]_ _|_ [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.1)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ _dtype__(__\[__(__'ev'__,_ _' 0: print( 'ask: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): qty \= depth.bid\_qty\_at\_tick(price\_tick) if qty \> 0: print( 'bid: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[2\]: import numpy as np btcusdt\_20240809 \= np.load('usdm/btcusdt\_20240809.npz')\['data'\] btcusdt\_20240808\_eod \= np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\] \[3\]: from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_3depth(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Efficient Market Depth Access[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Efficient-Market-Depth-Access "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `ROIVectorMarketDepth` provides more efficient market depth access through a vector that holds a limited price range of interest. The backtester using this feature can be created by `ROIVectorMarketDepthBacktest`. \[4\]: from numba import njit @njit def print\_3depth\_fast(hbt): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(60\_000\_000\_000) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = price / tick\_size i \= 0 for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): \# depth.ask\_depth returns the ask depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick qty \= depth.ask\_depth\[t\] if qty \> 0: print( 'ask: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): \# depth.bid\_depth returns the bid depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick qty \= depth.bid\_depth\[t\] if qty \> 0: print( 'bid: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[5\]: from hftbacktest import ROIVectorMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# Sets the lower bound price for the range of interest in the market depth. .roi\_lb(30000) \# Sets the upper bound price for the range of interest in the market depth. .roi\_ub(90000) ) \[6\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_3depth\_fast(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Order-Book-Imbalance "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def orderbookimbalance(hbt, out): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(10 \* 1e9) \== 0: depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 sum\_ask\_qty\_50bp \= 0.0 sum\_ask\_qty \= 0.0 for price\_tick in range(depth.best\_ask\_tick, roi\_ub\_tick + 1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick ask\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (ask\_price \- mid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_ask\_qty += depth.ask\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_ask\_qty\_50bp \= sum\_ask\_qty sum\_bid\_qty\_50bp \= 0.0 sum\_bid\_qty \= 0.0 for price\_tick in range(depth.best\_bid\_tick, roi\_lb\_tick \- 1, \-1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick bid\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (mid\_price \- bid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_bid\_qty += depth.bid\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_bid\_qty\_50bp \= sum\_bid\_qty imbalance\_50bp \= sum\_bid\_qty\_50bp \- sum\_ask\_qty\_50bp imbalance\_1pct \= sum\_bid\_qty \- sum\_ask\_qty imbalance\_tob \= depth.bid\_depth\[depth.best\_bid\_tick \- roi\_lb\_tick\] \- depth.ask\_depth\[depth.best\_ask\_tick \- roi\_lb\_tick\] out.append((hbt.current\_timestamp, imbalance\_tob, imbalance\_50bp, imbalance\_1pct)) return True \[8\]: from numba.typed import List from numba.types import Tuple, float64 hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) orderbookimbalance(hbt, out) \_ \= hbt.close() \[9\]: import polars as pl df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', 'TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[9\]: shape: (30, 4) | Local Timestamp | TOB Imbalance | 0.5% Imbalance | 1% Imbalance | | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 2.729 | \-1748.101 | \-3908.736 | | 2024-08-09 00:00:21.500 | 4.623 | \-1749.435 | \-3512.845 | | 2024-08-09 00:00:31.500 | \-6.465 | \-1259.897 | \-3357.755 | | 2024-08-09 00:00:41.500 | \-7.922 | \-1174.185 | \-3471.955 | | 2024-08-09 00:00:51.500 | \-2.484 | \-1147.597 | \-3461.48 | | … | … | … | … | | 2024-08-09 00:04:21.500 | 3.828 | \-1186.236 | \-3551.78 | | 2024-08-09 00:04:31.500 | \-1.35 | \-1332.379 | \-3517.854 | | 2024-08-09 00:04:41.500 | \-3.754 | \-1166.521 | \-2693.672 | | 2024-08-09 00:04:51.500 | \-2.525 | \-1188.56 | \-2716.914 | | 2024-08-09 00:05:01.500 | 1.91 | \-594.991 | \-2138.82 | \[10\]: from matplotlib import pyplot pyplot.plot(df\['Local Timestamp'\], df\['TOB Imbalance'\]) pyplot.plot(df\['Local Timestamp'\], df\['0.5% Imbalance'\]) pyplot.plot(df\['Local Timestamp'\], df\['1% Imbalance'\]) \[10\]: \[\] ![../_images/tutorials_Working_with_Market_Depth_and_Trades_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Working_with_Market_Depth_and_Trades_13_1.png) Display last trades between the step[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-last-trades-between-the-step "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[11\]: from hftbacktest import BUY\_EVENT @njit def print\_trades(hbt): while hbt.elapse(60 \* 1e9) \== 0: print('-------------------------------------------------------------------------------') print('current\_timestamp:', hbt.current\_timestamp) \# Gets the last trades occurring in the market, not the trades of our orders. last\_trades \= hbt.last\_trades(0) num \= 0 for last\_trade in last\_trades: if num \> 10: print('...') break print( 'exch\_timestamp:', last\_trade.exch\_ts, 'buy' if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT else 'sell', last\_trade.qty, '@', last\_trade.px ) num += 1 \# To prevent accumulating all last trades, which may cause a slowdown, \# clear\_last\_trades needs to be called. \# After this, accessing \`last\_trades\` will cause a crash. hbt.clear\_last\_trades(0) return True \[12\]: asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# To retrieve the last trades, \`last\_trades\_capacity\` should be set. .last\_trades\_capacity(1000) .roi\_lb(30000) .roi\_ub(90000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_trades(hbt) \_ \= hbt.close() \------------------------------------------------------------------------------- current\_timestamp: 1723161661500000000 exch\_timestamp: 1723161602372000000 buy 0.489 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.198 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.006 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.011 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.238 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.007 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.005 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161721500000000 exch\_timestamp: 1723161661697000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661724000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661751000000 buy 0.135 @ 61594.2 exch\_timestamp: 1723161661806000000 sell 1.328 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.006 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.32 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.032 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 1.208 @ 61594.1 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161781500000000 exch\_timestamp: 1723161721541000000 sell 0.002 @ 61576.5 exch\_timestamp: 1723161721574000000 buy 0.012 @ 61576.6 exch\_timestamp: 1723161721578000000 sell 0.003 @ 61576.5 exch\_timestamp: 1723161721583000000 buy 0.275 @ 61576.6 exch\_timestamp: 1723161721583000000 buy 0.469 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.095 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.102 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.197 @ 61576.6 exch\_timestamp: 1723161721586000000 buy 0.13 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.425 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.324 @ 61576.6 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161841500000000 exch\_timestamp: 1723161781628000000 sell 0.026 @ 61629.6 exch\_timestamp: 1723161781727000000 buy 0.011 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.05 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.006 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.007 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.075 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.065 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.247 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161901500000000 exch\_timestamp: 1723161841561000000 buy 0.01 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.006 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.002 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.022 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.097 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.014 @ 61621.6 exch\_timestamp: 1723161841565000000 buy 0.003 @ 61621.6 exch\_timestamp: 1723161841613000000 buy 0.002 @ 61622.5 exch\_timestamp: 1723161841613000000 buy 0.003 @ 61622.6 ... Rolling Volume-Weighted Average Price[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Rolling-Volume-Weighted-Average-Price "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[13\]: @njit def rolling\_vwap(hbt, out): buy\_amount\_bin \= np.zeros(100\_000, np.float64) buy\_qty\_bin \= np.zeros(100\_000, np.float64) sell\_amount\_bin \= np.zeros(100\_000, np.float64) sell\_qty\_bin \= np.zeros(100\_000, np.float64) idx \= 0 last\_trade\_price \= np.nan while hbt.elapse(10 \* 1e9) \== 0: last\_trades \= hbt.last\_trades(0) for last\_trade in last\_trades: if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT: buy\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty buy\_qty\_bin\[idx\] += last\_trade.qty else: sell\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty sell\_qty\_bin\[idx\] += last\_trade.qty hbt.clear\_last\_trades(0) idx += 1 if idx \>= 1: vwap10sec \= np.divide( buy\_amount\_bin\[idx \- 1\] + sell\_amount\_bin\[idx \- 1\], buy\_qty\_bin\[idx \- 1\] + sell\_qty\_bin\[idx \- 1\] ) else: vwap10sec \= np.nan if idx \>= 6: vwap1m \= np.divide( np.sum(buy\_amount\_bin\[idx \- 6:idx\]) + np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\]) + np.sum(sell\_qty\_bin\[idx \- 6:idx\]) ) buy\_vwap1m \= np.divide(np.sum(buy\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\])) sell\_vwap1m \= np.divide(np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(sell\_qty\_bin\[idx \- 6:idx\])) else: vwap1m \= np.nan buy\_vwap1m \= np.nan sell\_vwap1m \= np.nan out.append((hbt.current\_timestamp, vwap10sec, vwap1m, buy\_vwap1m, sell\_vwap1m)) return True \[14\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) rolling\_vwap(hbt, out) \_ \= hbt.close() \[15\]: df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', '10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[15\]: shape: (30, 5) | Local Timestamp | 10-sec VWAP | 1-min VWAP | 1-min Buy VWAP | 1-min Sell VWAP | | --- | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 61687.182976 | NaN | NaN | NaN | | 2024-08-09 00:00:21.500 | 61709.337576 | NaN | NaN | NaN | | 2024-08-09 00:00:31.500 | 61697.538054 | NaN | NaN | NaN | | 2024-08-09 00:00:41.500 | 61663.958879 | NaN | NaN | NaN | | 2024-08-09 00:00:51.500 | 61637.340621 | NaN | NaN | NaN | | … | … | … | … | … | | 2024-08-09 00:04:21.500 | 61643.009847 | 61624.459011 | 61626.495542 | 61622.549429 | | 2024-08-09 00:04:31.500 | 61670.795685 | 61635.877251 | 61638.362314 | 61632.48854 | | 2024-08-09 00:04:41.500 | 61643.108582 | 61641.846489 | 61648.672337 | 61636.032054 | | 2024-08-09 00:04:51.500 | 61614.723569 | 61640.490841 | 61647.769844 | 61634.372128 | | 2024-08-09 00:05:01.500 | 61584.697467 | 61637.334102 | 61642.209551 | 61632.12064 | \[16\]: pyplot.plot(df\['Local Timestamp'\], df\['10-sec VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min Buy VWAP'\]) pyplot.plot(df\['Local Timestamp'\], df\['1-min Sell VWAP'\]) \[16\]: \[\] ![../_images/tutorials_Working_with_Market_Depth_and_Trades_21_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Working_with_Market_Depth_and_Trades_21_1.png) --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ============================================================================================================================================================================================================================================================================================================================ For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Data Preparation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.4.2/index.html) * Data Preparation * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_sources/tutorials/Data%20Preparation.ipynb.txt) * * * Data Preparation[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Data%20Preparation.html#Data-Preparation "Link to this heading") =============================================================================================================================================== To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself. Getting started from Binance Futures’ raw feed data[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Data%20Preparation.html#Getting-started-from-Binance-Futures'-raw-feed-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can collect Binance Futures feed yourself using [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) . \[1\]: import gzip with gzip.open('usdm/btcusdt\_20240808.gz', 'r') as f: for i in range(5): line \= f.readline() print(line) b'1723161255030314667 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1723161256299,"T":1723161256298,"s":"BTCUSDT","U":5123107832006,"u":5123107837557,"pu":5123107831937,"b":\[\["58710.20","0.014"\],\["61496.50","0.010"\],\["61510.90","0.000"\],\["61641.50","1.211"\],\["61652.80","0.195"\],\["61653.30","0.072"\],\["61653.70","0.067"\],\["61657.90","0.067"\],\["61668.50","0.086"\],\["61670.60","0.161"\],\["61672.50","0.821"\],\["61673.60","0.048"\],\["61675.60","0.050"\],\["61684.50","0.765"\],\["61686.20","0.008"\],\["61701.80","0.331"\],\["61703.10","0.238"\],\["61715.90","0.308"\],\["61721.60","0.235"\],\["61724.10","0.002"\],\["61737.00","0.015"\],\["61739.00","0.000"\],\["61740.10","0.008"\],\["61740.50","12.111"\],\["61756.90","0.550"\],\["61758.70","0.003"\],\["61763.20","0.014"\],\["61764.10","0.168"\],\["61764.30","0.000"\],\["61765.50","0.000"\],\["61767.40","0.004"\],\["61768.20","0.120"\],\["61768.60","0.020"\],\["61768.90","0.099"\],\["61770.80","0.049"\],\["61771.10","0.612"\],\["61771.70","0.010"\],\["61773.50","0.035"\],\["61773.80","0.025"\],\["61774.00","0.112"\],\["61775.60","0.010"\],\["61776.00","0.084"\],\["61778.30","0.000"\],\["61778.60","0.408"\],\["61779.30","0.020"\],\["61779.60","0.220"\],\["61783.80","0.002"\],\["61784.90","0.102"\],\["61785.00","0.000"\],\["61788.10","0.140"\],\["61789.50","0.000"\],\["61798.70","0.153"\],\["61800.20","2.507"\]\],"a":\[\["61800.30","3.330"\],\["61804.60","0.057"\],\["61810.00","0.285"\],\["61812.00","0.732"\],\["61814.90","0.000"\],\["61817.20","0.000"\],\["61818.70","0.040"\],\["61824.00","0.860"\],\["61829.10","0.185"\],\["61831.30","0.008"\],\["61831.40","0.501"\],\["61839.00","0.002"\],\["61840.00","0.192"\],\["61856.30","0.003"\],\["61857.10","0.027"\],\["61857.40","0.000"\],\["61858.80","0.005"\],\["61858.90","0.032"\],\["61859.60","0.034"\],\["61874.80","0.006"\],\["61893.40","0.335"\],\["61911.90","0.014"\],\["61925.90","0.000"\],\["61930.50","0.015"\],\["61945.10","0.000"\],\["61953.70","0.000"\],\["62144.00","0.006"\],\["63113.70","0.000"\],\["65880.70","15.918"\]\]}}\\n' b'1723161255088169167 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107839020,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.510","T":1723161256313,"E":1723161256313}}\\n' b'1723161255088176367 {"stream":"btcusdt@trade","data":{"e":"trade","E":1723161256322,"T":1723161256322,"s":"BTCUSDT","t":5266583935,"p":"61800.30","q":"0.006","X":"MARKET","m":false}}\\n' b'1723161255088181667 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840008,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.504","T":1723161256322,"E":1723161256322}}\\n' b'1723161255088182467 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840016,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.522","T":1723161256322,"E":1723161256322}}\\n' The first token of the line is timestamp received by local. **Note:** The timestamp is in nanoseconds. The data needs to be converted to normalized data that can be fed into HftBacktest. `convert` method also attempts to correct timestamps by reordering the rows. \[2\]: import numpy as np from hftbacktest.data.utils import binancefutures data \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Normalized data as follows. You can find more details on [Data](https://hftbacktest.readthedocs.io/en/latest/data.html) . \[3\]: import polars as pl pl.DataFrame(data) \[3\]: shape: (491\_973, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 58710.2 | 0.014 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61496.5 | 0.01 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61510.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61641.5 | 1.211 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61652.8 | 0.195 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1723161600030000000 | 1723161600043617932 | 62292.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161600319000000 | 1723161600370793433 | 5000.0 | 2.321 | 0 | 0 | 0.0 | | 3489660929 | 1723161600709000000 | 1723161600760777134 | 61659.8 | 0.981 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61631.7 | 0.283 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61632.6 | 0.0 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. \[4\]: \_ \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', output\_filename\='usdm/btcusdt\_20240808.npz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Saving to usdm/btcusdt\_20240808.npz Creating a market depth snapshot[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Data%20Preparation.html#Creating-a-market-depth-snapshot "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As Binance Futures exchange runs 24/7, you need the initial snapshot to get the complete(almost) market depth. [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) fetches the snapshot only when it makes the connection, so you need build the initial snapshot from the start of the collected feed data. \[5\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. data \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001 ) Bid levels are shown before ask levels in the snapshot, and levels are sorted from the best price to the farthest price. \[6\]: pl.DataFrame(data) \[6\]: shape: (9\_597, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096388 | 0 | 0 | 61659.7 | 1.486 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61659.0 | 0.002 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.1 | 0.033 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.0 | 6.718 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61657.9 | 0.007 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660932 | 0 | 0 | 77354.3 | 0.015 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 77905.9 | 0.003 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 80000.0 | 10.708 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 104765.0 | 0.034 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 617050.0 | 0.003 | 0 | 0 | 0.0 | \[7\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240808\_eod.npz' ) \[8\]: \# Converts 20240809 data. \_ \= binancefutures.convert( 'usdm/btcusdt\_20240809.gz', output\_filename\='usdm/btcusdt\_20240809.npz', combined\_stream\=True ) \# Builds 20240809's last snapshot. \# Due to the file size limitation of GitHub, btcusdt\_20240809.npz does not contain data for the entire day. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz', initial\_snapshot\='usdm/btcusdt\_20240808\_eod.npz', ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1273873720 Correcting the event order Saving to usdm/btcusdt\_20240809.npz \[9\]: \# Builds 20240809's last snapshot without the initial snapshot. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last\_wo\_ss.npz' ) \# Builds the 20240809's last snapshot from 20240808 without the initial snapshot. \_ \= create\_last\_snapshot( \[\ 'usdm/btcusdt\_20240808.npz',\ 'usdm/btcusdt\_20240809.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz' ) Getting started from Tardis.dev data[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Data%20Preparation.html#Getting-started-from-Tardis.dev-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Few vendors offer tick-by-tick full market depth data along with snapshot and trade data, and Tardis.dev is among them. **Note:** Some data may have an issue with the exchange timestamp. Ideally, the exchange timestamp should reflect the moment the event occurs at the matching engine. However, some data uses the server’s data sent timestamp instead of the matching engine timestamp. \[10\]: \# https://docs.tardis.dev/historical-data-details/binance-futures \# Downloads sample Binance futures BTCUSDT trades !wget https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_trades.csv.gz \# Downloads sample Binance futures BTCUSDT book !wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_book.csv.gz \--2024-08-09 09:42:51-- https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.6.96, 104.18.7.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.6.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3090479 (2.9M) \[text/csv\] Saving to: ‘BTCUSDT\_trades.csv.gz’ BTCUSDT\_trades.csv. 100%\[===================>\] 2.95M 5.66MB/s in 0.5s 2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s 2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp. \[11\]: from hftbacktest.data.utils import tardis data \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\] ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order \[12\]: pl.DataFrame(data) \[12\]: shape: (27\_532\_602, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 | | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 | | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 | | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: \_ \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000 ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don’t need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev’s SOD snapshot in the converted file using the option. --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ============================================================================================================================================================================================================================================================================================================================ For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * JIT Compilation Overhead * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/jit_compilation_overhead.rst.txt) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/jit_compilation_overhead.html#jit-compilation-overhead "Link to this heading") =========================================================================================================================================================== HftBacktest takes advantage of Numba’s capabilities, relying on Numba JIT’ed classes. As a result, importing HftBacktest requires JIT compilation, which may take a few seconds. Additionally, the strategy function needs to be JIT’ed’ for performant backtesting, which also takes time to compile. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature. See the example below. from numba import njit \# May take a few seconds from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest \# Enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. asset \= ( BacktestAsset() .linear\_asset(1.0) .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .no\_partial\_fill\_exchange() .intp\_order\_latency(\[\ 'data/latency\_20221003.npz',\ 'data/latency\_20221004.npz',\ 'data/latency\_20221005.npz',\ 'data/latency\_20221006.npz',\ 'data/latency\_20221007.npz'\ \]) .power\_prob\_queue\_model3(3.0) .tick\_size(0.01) .lot\_size(0.001) .trading\_value\_fee\_model(0.0002, 0.0007) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) algo(arguments, hbt) --- # Latency Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Latency Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/latency_models.rst.txt) * * * Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#latency-models "Link to this heading") ============================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------- Latency is an important factor that you need to take into account when you backtest your HFT strategy. HftBacktest has three types of latencies. ![_images/latencies.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/latencies.png) * Feed latency This is the latency between the time the exchange sends the feed events such as order book change or trade and the time it is received by the local. This latency is dealt with through two different timestamps: local timestamp and exchange timestamp. * Order entry latency This is the latency between the time you send an order request and the time it is processed by the exchange’s matching engine. * Order response latency This is the latency between the time the exchange’s matching engine processes an order request and the time the order response is received by the local. The response to your order fill is also affected by this type of latency. ![_images/latency-comparison.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/latency-comparison.png) Order Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#order-latency-models "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- HftBacktest provides the following order latency models and you can also implement your own latency model. ### ConstantLatency[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#constantlatency "Link to this heading") It’s the most basic model that uses constant latencies. You just set the latencies. You can find details below. * [ConstantLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ConstantLatency.html) and [`constant_latency`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency "hftbacktest.BacktestAsset.constant_latency") ### IntpOrderLatency[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#intporderlatency "Link to this heading") This model interpolates order latency based on the actual order latency data. This is the most accurate among the provided models if you have the data with a fine time interval. You can collect the latency data by submitting unexecutable orders regularly. You can find details below. * [IntpOrderLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html) and [`intp_order_latency`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency "hftbacktest.BacktestAsset.intp_order_latency") **Data example** req\_ts (request timestamp at local), exch\_ts (exchange timestamp), resp\_ts (receipt timestamp at local), \_padding 1670026844751525000, 1670026844759000000, 1670026844762122000, 0 1670026845754020000, 1670026845762000000, 1670026845770003000, 0 ### FeedLatency[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#feedlatency "Link to this heading") If the live order latency data is unavailable, you can generate artificial order latency using feed latency. Please refer to [this tutorial](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Order%20Latency%20Data.html) for guidance. ### Implement your own order latency model[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/latency_models.html#implement-your-own-order-latency-model "Link to this heading") You need to implement the following trait. * [LatencyModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.LatencyModel.html) Please refer to [the latency model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/latency.rs) . --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Debugging Backtesting and Live Discrepancies * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/debugging_backtesting_and_live_discrepancies.rst.txt) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Link to this heading") ======================================================================================================================================================================================================================= Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as `PowerProbQueueModel` series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#order-fill-prob-queue-model) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # Statistics — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Statistics * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/reference/stats.rst.txt) * * * Statistics[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#statistics "Link to this heading") ====================================================================================================================== _class_ Stats[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/stats.html#Stats) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats "Link to this definition") **Example** import numpy as np from hftbacktest.stats import LinearAssetRecord asset0\_record \= np.load('backtest\_result.npz')\['0'\] stats \= ( LinearAssetRecord(asset0\_record) .resample('10s') .monthly() .stats(book\_size\=100000) ) stats.summary() stats.plot() Parameters: * **entire** (_DataFrame_) * **splits** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Mapping_](https://docs.python.org/3.10/library/typing.html#typing.Mapping "(in Python v3.10)")\ _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _,_ [_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _\]__\]_) * **kwargs** ([_Mapping_](https://docs.python.org/3.10/library/typing.html#typing.Mapping "(in Python v3.10)") _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _,_ [_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _\]_) summary(_pretty\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/stats.html#Stats.summary) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats.summary "Link to this definition") Displays the statistics summary. Parameters: **pretty** ([_bool_](https://docs.python.org/3.10/library/functions.html#bool "(in Python v3.10)") ) – Returns the statistics in a pretty-printed format. plot(_price\_as\_ret\=False_, _backend\='matplotlib'_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/stats.html#Stats.plot) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats.plot "Link to this definition") Plots the equity curves and positions over time along with the price chart. Parameters: * **price\_as\_ret** ([_bool_](https://docs.python.org/3.10/library/functions.html#bool "(in Python v3.10)") ) – Plots the price chart in cumulative returns if set to True; otherwise, it plots the price chart in raw price terms. * **backend** ([_Literal_](https://docs.python.org/3.10/library/typing.html#typing.Literal "(in Python v3.10)") _\[__'matplotlib'__,_ _'holoviews'__\]_) – Specifies which plotting library is used to plot the charts. The default is ‘matplotlib’. _class_ LinearAssetRecord(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/stats.html#LinearAssetRecord) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord "Link to this definition") Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.1)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.1)")\ _\[__\_ScalarType\_co__\]__\]_ _|_ _DataFrame_) contract\_size(_contract\_size_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size "Link to this definition") Sets the contract size. The default value is 1.0. Parameters: **contract\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The asset’s contract size. Return type: Self daily()[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily "Link to this definition") Generates daily statistics. Return type: Self monthly()[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly "Link to this definition") Generates monthly statistics. Return type: Self resample(_frequency_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample "Link to this definition") Sets the resampling frequency for downsampling the record. This could affect the calculation of the metrics related to the sampling interval. Additionally, it reduces the time required for computing the metrics and plotting the charts. The default value is 10s. Parameters: **frequency** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Interval of the window. This internally uses Polars, please see [polars.DataFrame.group\_by\_dynamic](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.group_by_dynamic.html) for more details. Return type: Self stats(_metrics\=None_, _\*\*kwargs_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats "Link to this definition") **Examples** stats \= record.stats(\[SR('SR365', trading\_days\_per\_year\=365), AnnualRet(trading\_days\_per\_year\=365)\] Parameters: * **metrics** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _|_ [_Type_](https://docs.python.org/3.10/library/typing.html#typing.Type "(in Python v3.10)")\ _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _\]__\]_ _|_ _None_) – The metrics specified in this list will be computed for the record. Each metric should be a class derived from the Metric class. If the class type, instead of an instance, is specified, an instance of the class will be constructed with the provided `kwargs`. The default value is a list of [`SR`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.SR "hftbacktest.stats.metrics.SR") , [`Sortino`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Sortino "hftbacktest.stats.metrics.Sortino") , [`Ret`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Ret "hftbacktest.stats.metrics.Ret") , [`MaxDrawdown`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "hftbacktest.stats.metrics.MaxDrawdown") , [`DailyNumberOfTrades`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "hftbacktest.stats.metrics.DailyNumberOfTrades") , [`DailyTradingValue`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "hftbacktest.stats.metrics.DailyTradingValue") , [`ReturnOverMDD`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "hftbacktest.stats.metrics.ReturnOverMDD") , `ReturnOverTrade`, and [`MaxPositionValue`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "hftbacktest.stats.metrics.MaxPositionValue") . * **kwargs** ([_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)") ) – Keyword arguments that will be used to construct the Metric instance. Returns: The statistics for the specified metrics of the record. Return type: [_Stats_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.stats.Stats") time\_unit(_time\_unit_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit "Link to this definition") Sets the time unit for converting timestamps in the records to datetime. The default value is ns. Parameters: **time\_unit** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – The unit of time of the timesteps since epoch time. This internally uses Polars, please see [polars.from\_epoch](https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.from_epoch.html) for more details. Return type: Self _class_ InverseAssetRecord(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/stats.html#InverseAssetRecord) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord "Link to this definition") Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.1)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.1)")\ _\[__\_ScalarType\_co__\]__\]_ _|_ _DataFrame_) contract\_size(_contract\_size_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size "Link to this definition") Sets the contract size. The default value is 1.0. Parameters: **contract\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The asset’s contract size. Return type: Self daily()[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily "Link to this definition") Generates daily statistics. Return type: Self monthly()[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly "Link to this definition") Generates monthly statistics. Return type: Self resample(_frequency_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample "Link to this definition") Sets the resampling frequency for downsampling the record. This could affect the calculation of the metrics related to the sampling interval. Additionally, it reduces the time required for computing the metrics and plotting the charts. The default value is 10s. Parameters: **frequency** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Interval of the window. This internally uses Polars, please see [polars.DataFrame.group\_by\_dynamic](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.group_by_dynamic.html) for more details. Return type: Self stats(_metrics\=None_, _\*\*kwargs_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats "Link to this definition") **Examples** stats \= record.stats(\[SR('SR365', trading\_days\_per\_year\=365), AnnualRet(trading\_days\_per\_year\=365)\] Parameters: * **metrics** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _|_ [_Type_](https://docs.python.org/3.10/library/typing.html#typing.Type "(in Python v3.10)")\ _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _\]__\]_ _|_ _None_) – The metrics specified in this list will be computed for the record. Each metric should be a class derived from the Metric class. If the class type, instead of an instance, is specified, an instance of the class will be constructed with the provided `kwargs`. The default value is a list of [`SR`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.SR "hftbacktest.stats.metrics.SR") , [`Sortino`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Sortino "hftbacktest.stats.metrics.Sortino") , [`Ret`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Ret "hftbacktest.stats.metrics.Ret") , [`MaxDrawdown`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "hftbacktest.stats.metrics.MaxDrawdown") , [`DailyNumberOfTrades`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "hftbacktest.stats.metrics.DailyNumberOfTrades") , [`DailyTradingValue`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "hftbacktest.stats.metrics.DailyTradingValue") , [`ReturnOverMDD`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "hftbacktest.stats.metrics.ReturnOverMDD") , `ReturnOverTrade`, and [`MaxPositionValue`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "hftbacktest.stats.metrics.MaxPositionValue") . * **kwargs** ([_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)") ) – Keyword arguments that will be used to construct the Metric instance. Returns: The statistics for the specified metrics of the record. Return type: [_Stats_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.stats.Stats") time\_unit(_time\_unit_)[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit "Link to this definition") Sets the time unit for converting timestamps in the records to datetime. The default value is ns. Parameters: **time\_unit** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – The unit of time of the timesteps since epoch time. This internally uses Polars, please see [polars.from\_epoch](https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.from_epoch.html) for more details. Return type: Self Metrics[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#metrics "Link to this heading") ---------------------------------------------------------------------------------------------------------------- _class_ Metric[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#Metric) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Metric "Link to this definition") A base class for computing a strategy’s performance metrics. Implementing a custom metric class derived from this base class enables the computation of the custom metric in the [`Stats`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.Stats") and displays the summary. _class_ Ret(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#Ret) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Ret "Link to this definition") Return Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is Return. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. _class_ AnnualRet(_name\=None_, _book\_size\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#AnnualRet) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.AnnualRet "Link to this definition") Annualised return Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is AnnualReturn. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The number of trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. _class_ SR(_name\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#SR) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.SR "Link to this definition") Sharpe Ratio without considering a benchmark. Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is SR. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – Trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. Additionally, be aware that to compute the daily Sharpe Ratio, it also multiplies by sqrt(the sample number per day), so the computed Sharpe Ratio is affected by the sampling interval. _class_ Sortino(_name\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#Sortino) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.Sortino "Link to this definition") Sortino Ratio without considering a benchmark. Parameters: * **name** – Name of this metric. The default value is Sortino. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – Trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. Additionally, be aware that to compute the daily Sharpe Ratio, it also multiplies by sqrt(the sample number per day), so the computed Sharpe Ratio is affected by the sampling interval. _class_ MaxDrawdown(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#MaxDrawdown) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "Link to this definition") Maximum Drawdown Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is MaxDrawdown. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. _class_ ReturnOverMDD(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#ReturnOverMDD) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "Link to this definition") Return over Maximum Drawdown Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is ReturnOverMDD. _class_ ReturnOverTrade(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#ReturnOverTrade) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade "Link to this definition") Return over Trade value, which represents the profit made per unit of trading value, for instance, $profit / $trading\_value. Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is ReturnOverTrade. _class_ NumberOfTrades(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#NumberOfTrades) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.NumberOfTrades "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ DailyNumberOfTrades(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#DailyNumberOfTrades) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ TradingVolume(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#TradingVolume) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.TradingVolume "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ DailyTradingVolume(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#DailyTradingVolume) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ TradingValue(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#TradingValue) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.TradingValue "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) _class_ DailyTradingValue(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#DailyTradingValue) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) _class_ MaxPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#MaxPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MeanPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#MeanPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MeanPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MedianPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#MedianPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MedianPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MaxLeverage(_name\=None_, _book\_size\=0.0_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/stats/metrics.html#MaxLeverage) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/stats.html#hftbacktest.stats.MaxLeverage "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) --- # Examples — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Examples * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/examples.rst.txt) * * * Examples[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/examples.html#examples "Link to this heading") ===================================================================================================================== You can find more examples [here](https://github.com/nkaz001/hftbacktest/tree/master/examples) --- # Migration to v2 — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Migration to v2 * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/migration2.rst.txt) * * * Migration to v2[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/migration2.html#migration-to-v2 "Link to this heading") =========================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/migration2.html#overview "Link to this heading") ------------------------------------------------------------------------------------------------------------- The migration from version 1 to version 2 introduces several significant changes that can cause errors if the same code is used without modification. It is highly recommended to review the updated tutorials. This guide aims to help you avoid common pitfalls during the migration process. Checking Success: Use `elapse() == 0`[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/migration2.html#checking-success-use-elapse-0 "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------- In version 1, `elapse` function returns `True` on success and `False` otherwise. Typically, the strategy loop checks for successful elapsing using `while elapse(duration)`. However, in version 2, elapse returns a code instead of a boolean, with `0` indicating success and any other value indicating an error. Consequently, the code should be updated to check if the return value equals `0`. For instance: `while elapse(duration) == 0` If the code remains unchanged, it will fail because a return value of `0` (indicating success) will be treated as `False`. Other methods that involve elapsing, such as `submit_buy_order` or `submit_sell_order`, also return a code similar to `elapse` instead of a boolean. Ensure to check if their return values equal `0` to confirm success instead of checking for `True`. Data Format Changes[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/migration2.html#data-format-changes "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- The data format fed into HftBacktest has undergone significant changes. It is strongly recommended to reprocess the data from raw data to preserve all information. However, if raw data is unavailable, [`the data conversion utility`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2 "hftbacktest.data.utils.migration2") from v1 to v2 is provided. The major changes are as follows: * SOA to AOS: The format has shifted from a columnar array (SOA) to a structured array (AOS). * Side Column Removal: `side` column has been removed. In version 2, the side is indicated by the `ev` field flags, [`BUY_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.BUY_EVENT "hftbacktest.types.BUY_EVENT") and [`SELL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.SELL_EVENT "hftbacktest.types.SELL_EVENT") . * Timestamp Handling: In version 1, the data utility corrects the event order by replacing one of the timestamps with `-1` to indicate an invalid event on either the exchange or the local side. In version 2, the validity of events on the exchange or local side is determined by ev field’s [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. * Timestamp Unit: Although not strictly enforced, the timestamp unit has changed from microseconds to nanoseconds. Additionally, the format for live order latency data has changed from SOA to AOS. --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Making Multiple Markets - Introduction * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb.txt) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Link to this heading") ======================================================================================================================================================================================================================= One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Data Preparation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Data Preparation * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Data%20Preparation.ipynb) * * * Data Preparation[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#Data-Preparation "Permalink to this heading") ================================================================================================================================================= To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself. Getting started from Binance Futures’ raw feed data[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#Getting-started-from-Binance-Futures'-raw-feed-data "Permalink to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can collect Binance Futures feed yourself using [https://github.com/nkaz001/collect-binancefutures](https://github.com/nkaz001/collect-binancefutures) \[1\]: import gzip with gzip.open('usdm/btcusdt\_20230404.dat.gz', 'r') as f: for i in range(20): line \= f.readline() print(line) b'1680652700423575 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246762461,"s":"BTCUSDT","b":"28145.10","B":"3.868","a":"28145.20","A":"6.887","T":1680652700430,"E":1680652700435}}\\n' b'1680652700441533 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700455,"T":1680652700452,"s":"BTCUSDT","t":3535186032,"p":"28145.10","q":"0.002","X":"MARKET","m":true}}\\n' b'1680652700441685 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700455,"T":1680652700452,"s":"BTCUSDT","t":3535186033,"p":"28145.10","q":"0.020","X":"MARKET","m":true}}\\n' b'1680652700441725 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700455,"T":1680652700452,"s":"BTCUSDT","t":3535186034,"p":"28145.10","q":"0.020","X":"MARKET","m":true}}\\n' b'1680652700442528 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700455,"T":1680652700452,"s":"BTCUSDT","t":3535186035,"p":"28145.10","q":"0.008","X":"MARKET","m":true}}\\n' b'1680652700442569 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246762974,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.887","T":1680652700452,"E":1680652700455}}\\n' b'1680652700454910 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700468,"T":1680652700462,"s":"BTCUSDT","t":3535186036,"p":"28145.20","q":"0.002","X":"MARKET","m":false}}\\n' b'1680652700455070 {"stream":"btcusdt@trade","data":{"e":"trade","E":1680652700468,"T":1680652700462,"s":"BTCUSDT","t":3535186037,"p":"28145.20","q":"0.008","X":"MARKET","m":false}}\\n' b'1680652700455110 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246763198,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.907","T":1680652700462,"E":1680652700468}}\\n' b'1680652700458611 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246763205,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.927","T":1680652700462,"E":1680652700469}}\\n' b'1680652700461970 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246763256,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.917","T":1680652700462,"E":1680652700470}}\\n' b'1680652700462351 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246763281,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.925","T":1680652700463,"E":1680652700471}}\\n' b'1680652700487340 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246763977,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.954","T":1680652700498,"E":1680652700501}}\\n' b'1680652700566269 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246765398,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.833","T":1680652700575,"E":1680652700579}}\\n' b'1680652700573952 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246765530,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.953","T":1680652700583,"E":1680652700588}}\\n' b'1680652700574554 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":2710246765551,"s":"BTCUSDT","b":"28145.10","B":"3.818","a":"28145.20","A":"6.961","T":1680652700585,"E":1680652700588}}\\n' b'1680652700599351 {"lastUpdateId": 2710246765483, "E": 1680652700590, "T": 1680652700580, "bids": \[\["28145.10", "3.818"\], \["28145.00", "0.413"\], \["28144.70", "0.002"\], \["28144.60", "0.023"\], \["28144.50", "0.003"\], \["28144.40", "2.430"\], \["28144.20", "0.022"\], \["28144.10", "0.055"\], \["28144.00", "0.160"\], \["28143.80", "0.049"\], \["28143.70", "0.002"\], \["28143.60", "1.151"\], \["28143.50", "0.001"\], \["28143.40", "0.009"\], \["28143.30", "0.008"\], \["28143.20", "0.713"\], \["28143.10", "0.006"\], \["28143.00", "1.633"\], \["28142.90", "0.005"\], \["28142.80", "0.027"\], \["28142.70", "0.180"\], \["28142.60", "0.066"\], \["28142.50", "0.895"\], \["28142.40", "1.654"\], \["28142.30", "0.004"\], \["28142.20", "0.470"\], \["28142.10", "0.054"\], \["28142.00", "0.631"\], \["28141.90", "1.312"\], \["28141.80", "0.046"\], \["28141.70", "0.044"\], \["28141.60", "0.067"\], \["28141.50", "0.110"\], \["28141.40", "1.124"\], \["28141.30", "4.160"\], \["28141.20", "3.110"\], \["28141.10", "0.012"\], \["28141.00", "0.010"\], \["28140.90", "0.201"\], \["28140.80", "0.054"\], \["28140.70", "0.011"\], \["28140.60", "0.324"\], \["28140.50", "0.509"\], \["28140.40", "0.071"\], \["28140.30", "0.207"\], \["28140.20", "0.983"\], \["28140.10", "0.039"\], \["28140.00", "1.007"\], \["28139.80", "0.046"\], \["28139.70", "0.382"\], \["28139.60", "1.200"\], \["28139.50", "0.413"\], \["28139.40", "0.222"\], \["28139.30", "0.406"\], \["28139.20", "1.656"\], \["28139.10", "0.030"\], \["28139.00", "2.014"\], \["28138.90", "0.017"\], \["28138.80", "0.004"\], \["28138.70", "0.099"\], \["28138.60", "1.580"\], \["28138.50", "5.951"\], \["28138.40", "0.537"\], \["28138.30", "0.002"\], \["28138.20", "0.603"\], \["28138.10", "0.004"\], \["28138.00", "0.010"\], \["28137.90", "0.196"\], \["28137.80", "0.178"\], \["28137.70", "1.191"\], \["28137.60", "2.539"\], \["28137.50", "0.013"\], \["28137.40", "0.903"\], \["28137.30", "0.002"\], \["28137.20", "1.023"\], \["28137.10", "1.874"\], \["28137.00", "4.064"\], \["28136.90", "2.793"\], \["28136.80", "0.008"\], \["28136.70", "0.553"\], \["28136.60", "0.017"\], \["28136.50", "0.010"\], \["28136.40", "0.005"\], \["28136.30", "0.022"\], \["28136.20", "0.282"\], \["28136.10", "8.013"\], \["28136.00", "0.278"\], \["28135.90", "0.045"\], \["28135.80", "2.203"\], \["28135.70", "2.601"\], \["28135.60", "1.460"\], \["28135.50", "0.050"\], \["28135.40", "0.345"\], \["28135.30", "0.256"\], \["28135.20", "0.005"\], \["28135.10", "0.267"\], \["28135.00", "0.160"\], \["28134.90", "0.002"\], \["28134.80", "1.929"\], \["28134.70", "0.090"\], \["28134.60", "1.176"\], \["28134.50", "0.715"\], \["28134.40", "3.618"\], \["28134.30", "15.128"\], \["28134.20", "2.789"\], \["28134.10", "0.032"\], \["28134.00", "0.334"\], \["28133.90", "0.048"\], \["28133.80", "1.652"\], \["28133.60", "0.054"\], \["28133.50", "0.002"\], \["28133.40", "0.315"\], \["28133.30", "1.448"\], \["28133.20", "0.036"\], \["28133.10", "0.004"\], \["28133.00", "0.200"\], \["28132.90", "0.760"\], \["28132.80", "0.476"\], \["28132.70", "0.063"\], \["28132.60", "0.449"\], \["28132.40", "0.007"\], \["28132.30", "2.406"\], \["28132.20", "10.661"\], \["28132.10", "0.008"\], \["28132.00", "0.512"\], \["28131.90", "0.794"\], \["28131.80", "0.054"\], \["28131.70", "1.402"\], \["28131.60", "1.305"\], \["28131.50", "1.800"\], \["28131.40", "0.201"\], \["28131.20", "0.054"\], \["28131.10", "2.420"\], \["28131.00", "0.296"\], \["28130.90", "0.008"\], \["28130.80", "0.011"\], \["28130.70", "0.102"\], \["28130.60", "0.001"\], \["28130.50", "0.078"\], \["28130.40", "0.002"\], \["28130.30", "0.004"\], \["28130.20", "1.234"\], \["28130.10", "0.060"\], \["28130.00", "0.146"\], \["28129.80", "0.013"\], \["28129.70", "0.055"\], \["28129.60", "0.819"\], \["28129.50", "0.890"\], \["28129.40", "2.296"\], \["28129.30", "1.003"\], \["28129.20", "3.000"\], \["28129.10", "0.080"\], \["28129.00", "1.844"\], \["28128.90", "2.610"\], \["28128.80", "10.346"\], \["28128.70", "2.683"\], \["28128.60", "1.148"\], \["28128.30", "0.064"\], \["28128.20", "0.001"\], \["28128.10", "13.066"\], \["28128.00", "0.316"\], \["28127.90", "0.001"\], \["28127.70", "3.002"\], \["28127.60", "0.008"\], \["28127.50", "0.468"\], \["28127.40", "2.042"\], \["28127.30", "0.061"\], \["28127.20", "2.208"\], \["28127.10", "0.086"\], \["28127.00", "0.109"\], \["28126.90", "0.386"\], \["28126.80", "0.132"\], \["28126.70", "7.302"\], \["28126.60", "10.660"\], \["28126.30", "0.045"\], \["28126.10", "4.328"\], \["28126.00", "1.750"\], \["28125.90", "1.007"\], \["28125.80", "0.004"\], \["28125.70", "0.038"\], \["28125.60", "0.192"\], \["28125.50", "0.001"\], \["28125.40", "0.706"\], \["28125.30", "0.003"\], \["28125.20", "0.097"\], \["28125.10", "8.451"\], \["28125.00", "25.287"\], \["28124.90", "1.010"\], \["28124.60", "0.004"\], \["28124.50", "0.012"\], \["28124.40", "0.413"\], \["28124.20", "0.466"\], \["28124.10", "1.475"\], \["28124.00", "0.531"\], \["28123.90", "0.020"\], \["28123.80", "0.001"\], \["28123.70", "2.028"\], \["28123.50", "0.002"\], \["28123.40", "0.036"\], \["28123.30", "0.208"\], \["28123.20", "0.839"\], \["28123.10", "0.009"\], \["28123.00", "2.830"\], \["28122.90", "0.530"\], \["28122.80", "0.087"\], \["28122.70", "0.215"\], \["28122.60", "0.251"\], \["28122.50", "0.308"\], \["28122.40", "0.062"\], \["28122.30", "1.864"\], \["28122.20", "0.027"\], \["28122.10", "0.002"\], \["28122.00", "0.103"\], \["28121.90", "0.811"\], \["28121.80", "8.052"\], \["28121.70", "0.806"\], \["28121.50", "0.064"\], \["28121.40", "1.047"\], \["28121.30", "0.002"\], \["28121.20", "0.232"\], \["28121.10", "0.002"\], \["28121.00", "0.772"\], \["28120.90", "0.135"\], \["28120.80", "0.042"\], \["28120.70", "0.212"\], \["28120.60", "0.011"\], \["28120.50", "3.413"\], \["28120.10", "0.156"\], \["28120.00", "3.143"\], \["28119.80", "0.605"\], \["28119.60", "0.919"\], \["28119.40", "0.404"\], \["28119.20", "1.333"\], \["28119.10", "0.215"\], \["28119.00", "3.003"\], \["28118.90", "0.767"\], \["28118.80", "3.326"\], \["28118.70", "0.010"\], \["28118.60", "0.153"\], \["28118.30", "0.033"\], \["28118.10", "0.007"\], \["28117.90", "1.959"\], \["28117.60", "2.722"\], \["28117.50", "11.443"\], \["28117.40", "0.007"\], \["28117.30", "2.235"\], \["28117.10", "0.744"\], \["28117.00", "0.223"\], \["28116.90", "3.895"\], \["28116.80", "1.510"\], \["28116.70", "0.003"\], \["28116.60", "0.003"\], \["28116.50", "4.452"\], \["28116.40", "6.051"\], \["28116.30", "2.006"\], \["28116.20", "3.300"\], \["28116.10", "2.584"\], \["28116.00", "0.067"\], \["28115.80", "0.030"\], \["28115.70", "0.001"\], \["28115.60", "0.062"\], \["28115.50", "0.727"\], \["28115.40", "1.641"\], \["28115.30", "3.191"\], \["28115.20", "0.065"\], \["28115.10", "0.011"\], \["28115.00", "3.178"\], \["28114.90", "0.001"\], \["28114.80", "0.107"\], \["28114.70", "0.133"\], \["28114.50", "4.029"\], \["28114.40", "3.876"\], \["28114.20", "0.827"\], \["28114.10", "0.077"\], \["28114.00", "0.796"\], \["28113.90", "0.409"\], \["28113.80", "0.266"\], \["28113.70", "3.197"\], \["28113.60", "3.032"\], \["28113.50", "0.068"\], \["28113.40", "1.207"\], \["28113.30", "3.000"\], \["28113.20", "0.213"\], \["28113.10", "3.016"\], \["28113.00", "0.472"\], \["28112.70", "6.047"\], \["28112.60", "2.034"\], \["28112.50", "1.369"\], \["28112.40", "1.452"\], \["28112.30", "3.105"\], \["28112.20", "0.212"\], \["28112.00", "5.929"\], \["28111.90", "0.951"\], \["28111.80", "3.270"\], \["28111.60", "13.388"\], \["28111.50", "0.055"\], \["28111.40", "0.005"\], \["28111.30", "2.748"\], \["28111.20", "0.495"\], \["28111.10", "6.307"\], \["28111.00", "1.337"\], \["28110.90", "11.898"\], \["28110.70", "3.402"\], \["28110.60", "0.213"\], \["28110.40", "0.019"\], \["28110.20", "0.388"\], \["28110.10", "0.701"\], \["28110.00", "6.217"\], \["28109.90", "0.002"\], \["28109.80", "0.102"\], \["28109.60", "1.655"\], \["28109.50", "0.010"\], \["28109.40", "0.032"\], \["28109.30", "0.001"\], \["28109.10", "0.243"\], \["28109.00", "0.037"\], \["28108.90", "1.974"\], \["28108.80", "1.373"\], \["28108.70", "1.302"\], \["28108.60", "4.321"\], \["28108.50", "0.102"\], \["28108.40", "2.026"\], \["28108.30", "0.030"\], \["28108.20", "5.421"\], \["28108.10", "0.223"\], \["28108.00", "0.091"\], \["28107.90", "0.075"\], \["28107.80", "0.015"\], \["28107.60", "0.200"\], \["28107.50", "0.002"\], \["28107.40", "3.469"\], \["28107.30", "0.284"\], \["28107.20", "1.545"\], \["28107.10", "6.866"\], \["28107.00", "0.213"\], \["28106.90", "0.524"\], \["28106.80", "3.141"\], \["28106.70", "0.007"\], \["28106.60", "3.101"\], \["28106.50", "0.369"\], \["28106.40", "0.258"\], \["28106.30", "0.785"\], \["28106.20", "0.111"\], \["28106.10", "0.553"\], \["28106.00", "1.088"\], \["28105.60", "0.953"\], \["28105.30", "0.533"\], \["28105.20", "2.129"\], \["28105.10", "0.003"\], \["28105.00", "0.978"\], \["28104.80", "0.030"\], \["28104.70", "0.001"\], \["28104.60", "0.209"\], \["28104.50", "0.002"\], \["28104.40", "0.317"\], \["28104.30", "0.917"\], \["28104.20", "0.008"\], \["28104.10", "0.048"\], \["28104.00", "0.939"\], \["28103.90", "0.232"\], \["28103.80", "0.548"\], \["28103.70", "0.270"\], \["28103.60", "0.010"\], \["28103.40", "0.012"\], \["28103.30", "0.006"\], \["28103.20", "0.213"\], \["28103.10", "0.351"\], \["28103.00", "0.251"\], \["28102.90", "0.884"\], \["28102.80", "6.814"\], \["28102.70", "1.471"\], \["28102.60", "30.273"\], \["28102.50", "1.525"\], \["28102.40", "0.049"\], \["28102.30", "7.455"\], \["28102.20", "0.072"\], \["28102.10", "1.428"\], \["28102.00", "0.204"\], \["28101.90", "6.973"\], \["28101.80", "0.001"\], \["28101.70", "18.887"\], \["28101.60", "0.247"\], \["28101.50", "2.869"\], \["28101.40", "0.268"\], \["28101.30", "30.271"\], \["28101.20", "0.062"\], \["28101.10", "0.660"\], \["28101.00", "0.113"\], \["28100.90", "0.205"\], \["28100.70", "0.124"\], \["28100.60", "0.181"\], \["28100.50", "1.703"\], \["28100.40", "0.275"\], \["28100.30", "0.001"\], \["28100.20", "0.212"\], \["28100.10", "1.573"\], \["28100.00", "2.727"\], \["28099.90", "6.474"\], \["28099.70", "0.099"\], \["28099.60", "0.056"\], \["28099.50", "0.360"\], \["28099.40", "0.996"\], \["28099.20", "0.406"\], \["28099.10", "0.478"\], \["28099.00", "3.469"\], \["28098.90", "1.864"\], \["28098.80", "1.804"\], \["28098.70", "0.092"\], \["28098.60", "0.002"\], \["28098.50", "0.119"\], \["28098.40", "0.093"\], \["28098.30", "0.002"\], \["28098.20", "0.729"\], \["28098.10", "0.070"\], \["28098.00", "1.096"\], \["28097.90", "0.054"\], \["28097.80", "2.228"\], \["28097.70", "0.448"\], \["28097.60", "0.070"\], \["28097.50", "1.361"\], \["28097.40", "1.100"\], \["28097.30", "0.446"\], \["28097.20", "1.831"\], \["28097.10", "4.399"\], \["28097.00", "0.074"\], \["28096.90", "0.327"\], \["28096.80", "0.226"\], \["28096.70", "0.244"\], \["28096.60", "0.312"\], \["28096.50", "1.530"\], \["28096.40", "0.166"\], \["28096.30", "0.208"\], \["28096.20", "5.612"\], \["28096.10", "0.587"\], \["28096.00", "0.139"\], \["28095.90", "2.851"\], \["28095.80", "0.129"\], \["28095.70", "0.101"\], \["28095.60", "0.088"\], \["28095.40", "3.064"\], \["28095.30", "1.375"\], \["28095.20", "0.187"\], \["28095.10", "0.225"\], \["28095.00", "0.811"\], \["28094.90", "0.801"\], \["28094.80", "0.174"\], \["28094.70", "0.295"\], \["28094.60", "0.421"\], \["28094.50", "0.060"\], \["28094.40", "0.660"\], \["28094.30", "0.371"\], \["28094.20", "0.169"\], \["28094.10", "2.136"\], \["28094.00", "0.183"\], \["28093.90", "0.130"\], \["28093.80", "5.429"\], \["28093.70", "13.389"\], \["28093.60", "0.093"\], \["28093.40", "0.159"\], \["28093.30", "0.005"\], \["28093.20", "0.164"\], \["28093.10", "0.038"\], \["28093.00", "3.091"\], \["28092.80", "0.003"\], \["28092.70", "3.147"\], \["28092.60", "0.117"\], \["28092.50", "1.473"\], \["28092.40", "8.439"\], \["28092.30", "2.853"\], \["28092.20", "1.371"\], \["28092.10", "10.690"\], \["28092.00", "0.058"\], \["28091.90", "0.024"\], \["28091.70", "4.074"\], \["28091.60", "0.004"\], \["28091.50", "0.231"\], \["28091.30", "0.618"\], \["28091.20", "1.096"\], \["28091.00", "0.542"\], \["28090.90", "0.123"\], \["28090.80", "0.108"\], \["28090.70", "0.350"\], \["28090.60", "0.138"\], \["28090.50", "1.754"\], \["28090.40", "2.112"\], \["28090.30", "0.181"\], \["28090.20", "5.214"\], \["28090.10", "0.422"\], \["28090.00", "0.254"\], \["28089.90", "2.105"\], \["28089.80", "19.973"\], \["28089.60", "5.150"\], \["28089.50", "0.256"\], \["28089.40", "1.079"\], \["28089.30", "1.593"\], \["28089.20", "5.242"\], \["28089.10", "0.312"\], \["28089.00", "1.047"\], \["28088.90", "4.891"\], \["28088.80", "0.166"\], \["28088.70", "0.001"\], \["28088.60", "0.144"\], \["28088.50", "0.140"\], \["28088.40", "2.132"\], \["28088.20", "0.068"\], \["28088.10", "6.753"\], \["28088.00", "1.447"\], \["28087.90", "0.412"\], \["28087.80", "4.234"\], \["28087.70", "0.311"\], \["28087.60", "1.810"\], \["28087.50", "0.031"\], \["28087.40", "1.345"\], \["28087.30", "0.267"\], \["28087.20", "0.324"\], \["28087.10", "0.003"\], \["28087.00", "0.650"\], \["28086.90", "5.378"\], \["28086.80", "0.656"\], \["28086.70", "2.352"\], \["28086.60", "0.131"\], \["28086.50", "0.265"\], \["28086.40", "0.174"\], \["28086.30", "0.300"\], \["28086.20", "0.207"\], \["28086.10", "0.401"\], \["28086.00", "1.076"\], \["28085.90", "0.475"\], \["28085.80", "3.487"\], \["28085.70", "5.414"\], \["28085.60", "0.086"\], \["28085.50", "0.144"\], \["28085.40", "3.595"\], \["28085.30", "0.289"\], \["28085.20", "2.669"\], \["28085.10", "0.007"\], \["28085.00", "14.256"\], \["28084.90", "0.343"\], \["28084.80", "0.421"\], \["28084.70", "0.481"\], \["28084.60", "0.390"\], \["28084.50", "0.187"\], \["28084.40", "0.224"\], \["28084.20", "0.085"\], \["28084.10", "0.059"\], \["28084.00", "7.846"\], \["28083.90", "3.144"\], \["28083.80", "3.763"\], \["28083.70", "0.006"\], \["28083.60", "0.082"\], \["28083.50", "0.296"\], \["28083.40", "0.124"\], \["28083.30", "9.287"\], \["28083.10", "29.720"\], \["28083.00", "125.094"\], \["28082.80", "0.091"\], \["28082.70", "0.536"\], \["28082.60", "0.014"\], \["28082.50", "0.091"\], \["28082.40", "0.086"\], \["28082.30", "3.703"\], \["28082.20", "0.032"\], \["28082.10", "3.001"\], \["28082.00", "0.024"\], \["28081.90", "0.307"\], \["28081.80", "0.114"\], \["28081.70", "0.037"\], \["28081.60", "0.141"\], \["28081.50", "0.138"\], \["28081.40", "3.000"\], \["28081.30", "0.210"\], \["28081.20", "0.211"\], \["28081.10", "0.199"\], \["28081.00", "0.146"\], \["28080.90", "0.632"\], \["28080.70", "0.362"\], \["28080.60", "0.164"\], \["28080.50", "0.138"\], \["28080.40", "1.070"\], \["28080.30", "0.212"\], \["28080.20", "3.000"\], \["28080.00", "4.756"\], \["28079.90", "0.002"\], \["28079.80", "1.560"\], \["28079.70", "6.080"\], \["28079.60", "0.416"\], \["28079.50", "0.023"\], \["28079.40", "0.173"\], \["28079.20", "0.017"\], \["28079.10", "2.076"\], \["28079.00", "1.575"\], \["28078.90", "0.004"\], \["28078.80", "0.084"\], \["28078.70", "0.019"\], \["28078.60", "0.108"\], \["28078.50", "0.071"\], \["28078.40", "6.548"\], \["28078.30", "0.981"\], \["28078.20", "3.954"\], \["28078.10", "0.202"\], \["28078.00", "0.920"\], \["28077.90", "0.201"\], \["28077.80", "0.107"\], \["28077.70", "0.466"\], \["28077.60", "16.112"\], \["28077.50", "0.099"\], \["28077.40", "0.080"\], \["28077.30", "0.136"\], \["28077.20", "0.081"\], \["28077.10", "0.297"\], \["28077.00", "7.175"\], \["28076.90", "0.347"\], \["28076.80", "1.552"\], \["28076.70", "0.284"\], \["28076.60", "1.143"\], \["28076.50", "3.103"\], \["28076.30", "2.133"\], \["28076.10", "0.501"\], \["28076.00", "0.229"\], \["28075.70", "0.117"\], \["28075.60", "0.091"\], \["28075.50", "0.276"\], \["28075.40", "0.116"\], \["28075.30", "0.119"\], \["28075.20", "0.910"\], \["28075.10", "0.141"\], \["28075.00", "0.424"\], \["28074.90", "0.176"\], \["28074.80", "3.707"\], \["28074.70", "0.204"\], \["28074.60", "0.085"\], \["28074.50", "0.075"\], \["28074.40", "0.006"\], \["28074.30", "0.088"\], \["28074.20", "0.625"\], \["28074.10", "0.421"\], \["28074.00", "0.592"\], \["28073.90", "0.410"\], \["28073.80", "0.018"\], \["28073.70", "0.176"\], \["28073.60", "2.122"\], \["28073.50", "0.351"\], \["28073.40", "0.001"\], \["28073.30", "0.001"\], \["28073.10", "0.069"\], \["28073.00", "0.858"\], \["28072.90", "0.345"\], \["28072.80", "0.316"\], \["28072.70", "0.054"\], \["28072.60", "0.001"\], \["28072.50", "0.215"\], \["28072.40", "0.116"\], \["28072.20", "1.316"\], \["28072.10", "1.531"\], \["28072.00", "4.635"\], \["28071.90", "5.776"\], \["28071.80", "0.184"\], \["28071.70", "0.001"\], \["28071.60", "0.016"\], \["28071.40", "0.217"\], \["28071.30", "1.938"\], \["28071.20", "0.089"\], \["28071.10", "0.357"\], \["28071.00", "14.024"\], \["28070.90", "0.021"\], \["28070.80", "0.423"\], \["28070.70", "0.146"\], \["28070.60", "10.724"\], \["28070.50", "0.093"\], \["28070.40", "2.891"\], \["28070.30", "0.343"\], \["28070.20", "0.106"\], \["28070.10", "0.051"\], \["28070.00", "45.977"\], \["28069.90", "0.270"\], \["28069.80", "0.116"\], \["28069.70", "0.057"\], \["28069.60", "0.080"\], \["28069.40", "1.521"\], \["28069.30", "0.085"\], \["28069.20", "1.365"\], \["28069.10", "0.058"\], \["28069.00", "0.112"\], \["28068.90", "0.195"\], \["28068.80", "2.878"\], \["28068.70", "0.271"\], \["28068.60", "0.200"\], \["28068.50", "0.352"\], \["28068.40", "0.077"\], \["28068.30", "0.368"\], \["28068.20", "0.182"\], \["28068.10", "0.315"\], \["28068.00", "0.116"\], \["28067.90", "0.106"\], \["28067.80", "0.249"\], \["28067.70", "0.003"\], \["28067.60", "0.003"\], \["28067.50", "8.164"\], \["28067.40", "0.177"\], \["28067.30", "0.116"\], \["28067.20", "0.393"\], \["28067.10", "0.240"\], \["28067.00", "5.245"\], \["28066.90", "0.129"\], \["28066.80", "0.373"\], \["28066.70", "0.166"\], \["28066.60", "0.021"\], \["28066.50", "1.518"\], \["28066.40", "0.041"\], \["28066.30", "0.167"\], \["28066.20", "0.007"\], \["28066.10", "0.125"\], \["28066.00", "0.337"\], \["28065.80", "0.135"\], \["28065.70", "0.085"\], \["28065.60", "0.255"\], \["28065.50", "0.059"\], \["28065.40", "0.415"\], \["28065.30", "2.592"\], \["28065.20", "0.086"\], \["28065.10", "0.407"\], \["28065.00", "0.615"\], \["28064.90", "0.308"\], \["28064.80", "0.457"\], \["28064.70", "4.348"\], \["28064.60", "0.016"\], \["28064.50", "0.004"\], \["28064.40", "1.835"\], \["28064.20", "0.089"\], \["28064.10", "0.320"\], \["28064.00", "0.145"\], \["28063.90", "0.002"\], \["28063.70", "0.594"\], \["28063.60", "0.621"\], \["28063.50", "0.001"\], \["28063.40", "0.082"\], \["28063.30", "0.256"\], \["28063.20", "0.166"\], \["28063.10", "3.124"\], \["28063.00", "2.869"\], \["28062.90", "0.201"\], \["28062.80", "0.424"\], \["28062.70", "0.007"\], \["28062.60", "0.133"\], \["28062.50", "0.080"\], \["28062.40", "0.199"\], \["28062.20", "0.292"\], \["28062.10", "0.056"\], \["28062.00", "0.163"\], \["28061.90", "0.180"\], \["28061.80", "0.053"\], \["28061.70", "0.082"\], \["28061.60", "0.001"\], \["28061.50", "0.537"\], \["28061.40", "1.035"\], \["28061.30", "0.239"\], \["28061.20", "0.085"\], \["28061.10", "0.083"\], \["28061.00", "0.407"\], \["28060.90", "0.087"\], \["28060.80", "0.473"\], \["28060.70", "0.822"\], \["28060.60", "0.205"\], \["28060.50", "2.651"\], \["28060.40", "0.281"\], \["28060.30", "0.348"\], \["28060.20", "0.127"\], \["28060.10", "0.129"\], \["28060.00", "12.187"\], \["28059.90", "0.337"\], \["28059.80", "1.211"\], \["28059.70", "0.350"\], \["28059.60", "1.515"\], \["28059.50", "3.055"\], \["28059.40", "0.217"\], \["28059.30", "0.441"\], \["28059.20", "1.583"\], \["28059.10", "0.011"\], \["28059.00", "0.254"\], \["28058.90", "0.152"\], \["28058.80", "0.828"\], \["28058.70", "0.074"\], \["28058.60", "0.033"\], \["28058.50", "0.121"\], \["28058.40", "0.550"\], \["28058.30", "1.555"\], \["28058.20", "0.155"\], \["28058.10", "0.103"\], \["28058.00", "6.335"\], \["28057.90", "0.042"\], \["28057.80", "0.873"\], \["28057.70", "0.004"\], \["28057.60", "0.001"\], \["28057.50", "0.301"\], \["28057.30", "0.551"\], \["28057.20", "1.538"\], \["28057.10", "0.195"\], \["28057.00", "0.560"\], \["28056.90", "0.101"\], \["28056.80", "0.003"\], \["28056.70", "0.002"\], \["28056.60", "0.011"\], \["28056.50", "0.085"\], \["28056.40", "0.299"\], \["28056.30", "0.006"\], \["28056.20", "0.030"\], \["28056.10", "0.553"\], \["28056.00", "1.433"\], \["28055.90", "0.039"\], \["28055.80", "0.490"\], \["28055.70", "0.085"\], \["28055.60", "0.454"\], \["28055.50", "0.263"\], \["28055.40", "4.539"\], \["28055.30", "0.085"\], \["28055.20", "0.255"\], \["28055.10", "0.175"\], \["28055.00", "3.113"\], \["28054.90", "0.173"\], \["28054.80", "0.001"\], \["28054.70", "0.514"\], \["28054.60", "0.222"\], \["28054.50", "0.307"\], \["28054.40", "0.514"\], \["28054.30", "0.099"\], \["28054.20", "0.060"\], \["28054.10", "0.121"\], \["28054.00", "0.587"\], \["28053.90", "9.321"\], \["28053.80", "0.185"\], \["28053.70", "0.001"\], \["28053.60", "0.242"\], \["28053.50", "0.022"\], \["28053.40", "0.035"\], \["28053.30", "0.007"\], \["28053.10", "0.105"\], \["28053.00", "0.907"\], \["28052.90", "0.216"\], \["28052.80", "7.400"\], \["28052.60", "0.059"\], \["28052.50", "0.191"\], \["28052.40", "0.365"\], \["28052.30", "0.131"\], \["28052.20", "3.907"\], \["28052.10", "0.120"\], \["28052.00", "0.805"\], \["28051.90", "172.732"\], \["28051.80", "0.016"\], \["28051.70", "0.100"\], \["28051.60", "0.192"\], \["28051.50", "1.835"\], \["28051.40", "0.099"\], \["28051.20", "0.097"\], \["28051.10", "0.153"\], \["28051.00", "9.564"\], \["28050.90", "0.177"\], \["28050.80", "0.030"\], \["28050.70", "3.525"\], \["28050.60", "10.026"\], \["28050.50", "0.212"\], \["28050.40", "0.098"\], \["28050.30", "0.012"\], \["28050.10", "1.148"\], \["28050.00", "38.892"\], \["28049.90", "0.115"\], \["28049.80", "10.540"\], \["28049.70", "5.713"\], \["28049.60", "0.288"\], \["28049.50", "0.001"\], \["28049.40", "0.001"\], \["28049.30", "0.103"\], \["28049.20", "1.513"\], \["28049.10", "0.215"\], \["28049.00", "0.331"\], \["28048.80", "0.034"\], \["28048.70", "0.531"\], \["28048.60", "0.004"\], \["28048.50", "1.171"\], \["28048.40", "0.175"\], \["28048.30", "0.206"\], \["28048.10", "8.352"\], \["28048.00", "2.224"\], \["28047.90", "0.120"\], \["28047.80", "0.549"\], \["28047.70", "0.214"\], \["28047.60", "0.106"\], \["28047.50", "0.178"\], \["28047.40", "0.240"\], \["28047.30", "0.012"\], \["28047.20", "0.038"\], \["28047.10", "0.091"\], \["28047.00", "0.611"\], \["28046.90", "0.242"\], \["28046.80", "0.073"\], \["28046.70", "0.101"\], \["28046.60", "0.329"\], \["28046.30", "0.014"\], \["28046.20", "0.002"\], \["28046.10", "0.382"\], \["28046.00", "0.535"\], \["28045.90", "0.135"\], \["28045.60", "0.011"\], \["28045.50", "0.815"\], \["28045.40", "0.119"\], \["28045.30", "0.230"\], \["28045.20", "0.003"\], \["28045.10", "0.155"\], \["28045.00", "1.935"\], \["28044.90", "0.123"\], \["28044.80", "12.323"\], \["28044.70", "0.306"\], \["28044.60", "0.141"\], \["28044.50", "0.005"\], \["28044.40", "0.007"\], \["28044.30", "0.198"\], \["28044.20", "0.101"\], \["28044.10", "0.071"\], \["28044.00", "3.365"\], \["28043.90", "0.001"\], \["28043.80", "1.073"\], \["28043.70", "0.585"\], \["28043.60", "0.001"\], \["28043.50", "0.002"\], \["28043.30", "0.034"\], \["28043.20", "0.088"\], \["28043.10", "0.006"\], \["28043.00", "0.257"\], \["28042.90", "0.324"\], \["28042.70", "0.123"\], \["28042.60", "0.042"\], \["28042.50", "0.238"\], \["28042.40", "0.002"\], \["28042.30", "10.905"\], \["28042.20", "0.042"\], \["28042.10", "0.187"\], \["28042.00", "0.324"\], \["28041.90", "0.083"\], \["28041.80", "0.056"\], \["28041.70", "0.006"\], \["28041.60", "0.080"\], \["28041.50", "0.018"\], \["28041.40", "0.002"\], \["28041.30", "0.003"\], \["28041.20", "0.427"\], \["28041.10", "0.534"\], \["28041.00", "0.163"\], \["28040.90", "0.142"\], \["28040.80", "0.007"\], \["28040.60", "0.119"\], \["28040.50", "0.003"\], \["28040.40", "0.691"\], \["28040.30", "0.135"\], \["28040.20", "0.158"\], \["28040.10", "0.047"\], \["28040.00", "57.404"\], \["28039.90", "0.081"\], \["28039.80", "0.017"\], \["28039.70", "0.217"\], \["28039.60", "0.256"\], \["28039.50", "0.718"\], \["28039.40", "0.243"\], \["28039.30", "0.027"\], \["28039.20", "1.464"\], \["28039.10", "0.364"\], \["28039.00", "0.771"\], \["28038.90", "0.082"\], \["28038.80", "1.601"\], \["28038.70", "0.001"\], \["28038.60", "0.322"\], \["28038.40", "0.209"\], \["28038.30", "0.041"\], \["28038.20", "0.004"\], \["28038.00", "4.476"\], \["28037.90", "3.860"\], \["28037.80", "0.068"\], \["28037.70", "0.542"\], \["28037.60", "0.249"\], \["28037.50", "0.301"\], \["28037.40", "4.364"\], \["28037.30", "0.055"\], \["28037.20", "0.103"\], \["28037.10", "3.944"\], \["28037.00", "0.014"\], \["28036.90", "0.320"\], \["28036.80", "0.002"\], \["28036.70", "0.070"\], \["28036.60", "0.088"\], \["28036.50", "0.015"\], \["28036.40", "0.343"\], \["28036.30", "0.100"\], \["28036.20", "0.228"\], \["28036.10", "0.002"\], \["28036.00", "0.662"\], \["28035.90", "0.260"\], \["28035.80", "0.210"\], \["28035.70", "2.550"\], \["28035.60", "0.129"\], \["28035.50", "0.354"\], \["28035.40", "0.003"\], \["28035.30", "0.103"\]\], "asks": \[\["28145.20", "6.833"\], \["28145.30", "0.009"\], \["28145.40", "0.008"\], \["28145.60", "0.002"\], \["28145.70", "0.003"\], \["28145.90", "0.009"\], \["28146.00", "0.002"\], \["28146.10", "0.009"\], \["28146.20", "0.045"\], \["28146.40", "0.139"\], \["28146.50", "0.047"\], \["28146.60", "0.027"\], \["28146.70", "0.023"\], \["28146.80", "0.380"\], \["28146.90", "0.254"\], \["28147.00", "0.027"\], \["28147.10", "0.084"\], \["28147.20", "0.002"\], \["28147.30", "0.177"\], \["28147.50", "0.001"\], \["28147.70", "0.177"\], \["28147.80", "0.105"\], \["28147.90", "0.138"\], \["28148.00", "0.419"\], \["28148.10", "0.426"\], \["28148.20", "0.008"\], \["28148.30", "0.155"\], \["28148.40", "0.088"\], \["28148.50", "0.260"\], \["28148.60", "0.006"\], \["28148.70", "0.871"\], \["28148.80", "0.595"\], \["28148.90", "2.000"\], \["28149.00", "1.147"\], \["28149.10", "0.370"\], \["28149.20", "1.754"\], \["28149.40", "0.628"\], \["28149.50", "5.833"\], \["28149.60", "1.963"\], \["28149.70", "0.001"\], \["28149.80", "1.704"\], \["28149.90", "0.480"\], \["28150.00", "2.712"\], \["28150.10", "0.345"\], \["28150.20", "0.002"\], \["28150.40", "0.202"\], \["28150.50", "0.072"\], \["28150.60", "3.905"\], \["28150.70", "0.212"\], \["28150.90", "0.374"\], \["28151.00", "1.357"\], \["28151.10", "0.074"\], \["28151.20", "0.176"\], \["28151.30", "0.001"\], \["28151.40", "0.160"\], \["28151.60", "0.135"\], \["28152.00", "0.568"\], \["28152.10", "1.432"\], \["28152.20", "0.007"\], \["28152.30", "0.010"\], \["28152.40", "0.110"\], \["28152.60", "0.012"\], \["28152.70", "0.110"\], \["28152.80", "0.070"\], \["28152.90", "1.146"\], \["28153.00", "5.070"\], \["28153.10", "4.020"\], \["28153.20", "12.494"\], \["28153.30", "2.211"\], \["28153.50", "0.321"\], \["28153.60", "1.553"\], \["28153.70", "0.248"\], \["28153.80", "0.008"\], \["28153.90", "0.080"\], \["28154.00", "0.339"\], \["28154.10", "0.415"\], \["28154.20", "0.001"\], \["28154.30", "0.100"\], \["28154.40", "0.385"\], \["28154.50", "0.001"\], \["28154.60", "0.177"\], \["28154.70", "0.001"\], \["28154.80", "0.445"\], \["28154.90", "0.717"\], \["28155.00", "1.370"\], \["28155.10", "1.489"\], \["28155.30", "0.035"\], \["28155.40", "0.129"\], \["28155.60", "0.391"\], \["28155.70", "1.065"\], \["28155.80", "0.501"\], \["28155.90", "1.464"\], \["28156.00", "0.800"\], \["28156.10", "0.393"\], \["28156.20", "0.046"\], \["28156.30", "1.067"\], \["28156.40", "1.634"\], \["28156.50", "0.001"\], \["28156.60", "1.411"\], \["28156.70", "0.089"\], \["28156.80", "1.159"\], \["28157.00", "2.333"\], \["28157.10", "0.357"\], \["28157.20", "0.575"\], \["28157.30", "0.253"\], \["28157.40", "0.977"\], \["28157.50", "0.062"\], \["28157.60", "1.290"\], \["28157.70", "0.034"\], \["28157.80", "0.279"\], \["28157.90", "0.067"\], \["28158.00", "0.782"\], \["28158.10", "0.224"\], \["28158.20", "0.533"\], \["28158.30", "0.801"\], \["28158.40", "0.872"\], \["28158.50", "1.372"\], \["28158.60", "1.956"\], \["28158.70", "0.451"\], \["28158.80", "0.356"\], \["28158.90", "0.267"\], \["28159.00", "2.356"\], \["28159.10", "0.747"\], \["28159.20", "1.102"\], \["28159.30", "5.502"\], \["28159.40", "1.647"\], \["28159.50", "0.662"\], \["28159.60", "0.726"\], \["28159.70", "2.441"\], \["28159.80", "1.814"\], \["28159.90", "1.407"\], \["28160.00", "0.995"\], \["28160.10", "0.006"\], \["28160.20", "2.176"\], \["28160.30", "0.432"\], \["28160.40", "0.489"\], \["28160.60", "0.001"\], \["28160.70", "2.037"\], \["28160.80", "0.181"\], \["28160.90", "1.832"\], \["28161.00", "0.004"\], \["28161.10", "0.595"\], \["28161.20", "0.200"\], \["28161.30", "1.191"\], \["28161.40", "0.004"\], \["28161.50", "0.003"\], \["28161.60", "0.325"\], \["28161.80", "0.195"\], \["28161.90", "0.054"\], \["28162.00", "0.871"\], \["28162.20", "0.751"\], \["28162.30", "0.057"\], \["28162.40", "0.302"\], \["28162.50", "0.004"\], \["28162.70", "0.004"\], \["28162.80", "0.099"\], \["28162.90", "1.662"\], \["28163.10", "0.502"\], \["28163.30", "0.054"\], \["28163.40", "0.911"\], \["28163.50", "7.023"\], \["28163.60", "0.095"\], \["28163.70", "0.003"\], \["28163.80", "0.648"\], \["28163.90", "0.083"\], \["28164.00", "0.492"\], \["28164.10", "0.595"\], \["28164.20", "5.000"\], \["28164.40", "3.216"\], \["28164.50", "0.584"\], \["28164.60", "0.002"\], \["28164.70", "2.085"\], \["28164.80", "0.563"\], \["28164.90", "5.215"\], \["28165.00", "4.531"\], \["28165.10", "0.750"\], \["28165.20", "0.980"\], \["28165.30", "0.356"\], \["28165.40", "0.301"\], \["28165.50", "0.303"\], \["28165.60", "0.002"\], \["28165.70", "0.007"\], \["28165.80", "0.383"\], \["28165.90", "1.105"\], \["28166.00", "0.161"\], \["28166.20", "4.622"\], \["28166.30", "0.202"\], \["28166.40", "0.440"\], \["28166.50", "1.251"\], \["28166.60", "0.034"\], \["28166.70", "0.232"\], \["28166.90", "0.370"\], \["28167.00", "0.107"\], \["28167.10", "0.399"\], \["28167.20", "0.059"\], \["28167.30", "0.040"\], \["28167.40", "0.003"\], \["28167.60", "0.325"\], \["28167.70", "0.024"\], \["28167.80", "0.777"\], \["28167.90", "0.022"\], \["28168.00", "0.633"\], \["28168.10", "2.091"\], \["28168.20", "0.396"\], \["28168.30", "3.324"\], \["28168.40", "0.362"\], \["28168.50", "0.107"\], \["28168.60", "2.304"\], \["28168.80", "10.144"\], \["28169.00", "0.055"\], \["28169.10", "0.063"\], \["28169.20", "0.070"\], \["28169.30", "0.003"\], \["28169.40", "3.075"\], \["28169.50", "12.127"\], \["28169.60", "3.905"\], \["28169.70", "0.108"\], \["28169.80", "0.072"\], \["28169.90", "1.229"\], \["28170.00", "2.082"\], \["28170.10", "0.028"\], \["28170.20", "0.001"\], \["28170.30", "0.129"\], \["28170.50", "0.011"\], \["28170.60", "0.216"\], \["28170.80", "1.498"\], \["28170.90", "5.049"\], \["28171.10", "0.011"\], \["28171.20", "0.352"\], \["28171.50", "1.278"\], \["28171.70", "0.471"\], \["28171.80", "1.966"\], \["28171.90", "0.011"\], \["28172.00", "0.010"\], \["28172.10", "1.060"\], \["28172.20", "1.669"\], \["28172.30", "7.416"\], \["28172.60", "0.886"\], \["28172.70", "0.001"\], \["28172.80", "0.055"\], \["28172.90", "0.053"\], \["28173.00", "0.585"\], \["28173.10", "0.010"\], \["28173.30", "0.014"\], \["28173.40", "0.002"\], \["28173.50", "3.029"\], \["28173.70", "0.344"\], \["28173.80", "6.066"\], \["28173.90", "0.004"\], \["28174.00", "0.001"\], \["28174.10", "4.263"\], \["28174.20", "0.709"\], \["28174.30", "0.096"\], \["28174.40", "0.384"\], \["28174.60", "0.005"\], \["28174.70", "3.405"\], \["28174.80", "0.003"\], \["28175.00", "3.116"\], \["28175.10", "0.123"\], \["28175.20", "1.063"\], \["28175.40", "3.993"\], \["28175.50", "2.137"\], \["28175.60", "1.139"\], \["28175.70", "0.002"\], \["28175.80", "2.647"\], \["28176.00", "0.071"\], \["28176.10", "0.050"\], \["28176.20", "0.011"\], \["28176.40", "5.880"\], \["28176.50", "0.050"\], \["28176.60", "0.001"\], \["28176.70", "3.832"\], \["28176.80", "3.693"\], \["28177.10", "0.001"\], \["28177.20", "1.047"\], \["28177.30", "0.001"\], \["28177.40", "3.001"\], \["28177.60", "0.037"\], \["28177.80", "3.684"\], \["28177.90", "0.040"\], \["28178.10", "0.341"\], \["28178.20", "3.195"\], \["28178.40", "0.846"\], \["28178.50", "0.003"\], \["28178.60", "2.880"\], \["28178.70", "0.763"\], \["28178.80", "0.388"\], \["28178.90", "0.077"\], \["28179.10", "0.003"\], \["28179.20", "1.981"\], \["28179.30", "3.001"\], \["28179.40", "0.029"\], \["28179.50", "0.517"\], \["28179.60", "0.216"\], \["28179.80", "0.450"\], \["28179.90", "0.410"\], \["28180.00", "4.999"\], \["28180.10", "0.074"\], \["28180.20", "0.452"\], \["28180.30", "3.017"\], \["28180.50", "0.037"\], \["28180.70", "0.500"\], \["28180.90", "3.001"\], \["28181.00", "0.180"\], \["28181.20", "0.028"\], \["28181.30", "0.213"\], \["28181.40", "0.054"\], \["28181.60", "0.096"\], \["28181.70", "0.321"\], \["28181.80", "0.082"\], \["28181.90", "0.004"\], \["28182.00", "5.527"\], \["28182.10", "1.942"\], \["28182.20", "0.138"\], \["28182.30", "3.115"\], \["28182.40", "8.624"\], \["28182.50", "3.128"\], \["28182.60", "0.324"\], \["28182.70", "0.554"\], \["28182.90", "1.942"\], \["28183.00", "3.001"\], \["28183.10", "0.219"\], \["28183.20", "0.006"\], \["28183.30", "0.005"\], \["28183.50", "0.052"\], \["28183.70", "1.187"\], \["28184.00", "0.051"\], \["28184.10", "1.392"\], \["28184.20", "2.828"\], \["28184.30", "2.053"\], \["28184.40", "0.093"\], \["28184.50", "0.062"\], \["28184.60", "0.010"\], \["28184.70", "1.937"\], \["28184.80", "0.178"\], \["28184.90", "0.482"\], \["28185.00", "1.207"\], \["28185.10", "0.207"\], \["28185.20", "0.058"\], \["28185.40", "0.056"\], \["28185.50", "0.067"\], \["28185.70", "0.124"\], \["28185.80", "0.002"\], \["28185.90", "0.112"\], \["28186.00", "0.677"\], \["28186.20", "0.233"\], \["28186.30", "0.064"\], \["28186.40", "0.019"\], \["28186.50", "0.050"\], \["28186.60", "2.438"\], \["28186.70", "0.696"\], \["28186.80", "2.121"\], \["28186.90", "0.096"\], \["28187.00", "0.655"\], \["28187.10", "0.075"\], \["28187.20", "3.055"\], \["28187.30", "0.138"\], \["28187.40", "0.490"\], \["28187.50", "0.261"\], \["28187.60", "0.057"\], \["28187.70", "0.249"\], \["28187.80", "0.093"\], \["28187.90", "0.139"\], \["28188.00", "0.511"\], \["28188.10", "6.819"\], \["28188.20", "4.594"\], \["28188.30", "2.910"\], \["28188.50", "30.115"\], \["28188.60", "0.094"\], \["28188.70", "0.301"\], \["28188.80", "0.132"\], \["28189.00", "0.211"\], \["28189.10", "0.035"\], \["28189.20", "4.035"\], \["28189.30", "0.046"\], \["28189.40", "0.077"\], \["28189.50", "0.131"\], \["28189.60", "5.334"\], \["28189.70", "0.017"\], \["28189.80", "1.417"\], \["28189.90", "0.102"\], \["28190.00", "0.331"\], \["28190.10", "6.807"\], \["28190.20", "1.500"\], \["28190.30", "6.361"\], \["28190.40", "0.024"\], \["28190.50", "0.656"\], \["28190.60", "0.395"\], \["28190.70", "0.002"\], \["28190.80", "0.053"\], \["28190.90", "0.512"\], \["28191.00", "0.326"\], \["28191.10", "0.208"\], \["28191.20", "0.014"\], \["28191.30", "0.001"\], \["28191.40", "0.365"\], \["28191.50", "0.316"\], \["28191.70", "0.070"\], \["28191.80", "3.992"\], \["28191.90", "0.053"\], \["28192.00", "0.223"\], \["28192.10", "0.078"\], \["28192.20", "10.683"\], \["28192.30", "0.001"\], \["28192.40", "0.054"\], \["28192.50", "0.010"\], \["28192.60", "0.011"\], \["28192.70", "0.121"\], \["28192.80", "0.367"\], \["28192.90", "0.264"\], \["28193.00", "0.025"\], \["28193.10", "0.059"\], \["28193.20", "0.108"\], \["28193.30", "0.101"\], \["28193.40", "0.948"\], \["28193.50", "4.147"\], \["28193.60", "0.265"\], \["28193.70", "0.014"\], \["28193.80", "0.011"\], \["28194.00", "2.695"\], \["28194.10", "3.476"\], \["28194.20", "0.559"\], \["28194.30", "0.014"\], \["28194.40", "0.387"\], \["28194.50", "0.100"\], \["28194.60", "0.108"\], \["28194.70", "0.111"\], \["28194.80", "0.912"\], \["28194.90", "0.115"\], \["28195.00", "1.881"\], \["28195.20", "0.117"\], \["28195.30", "0.166"\], \["28195.40", "4.603"\], \["28195.50", "0.056"\], \["28195.60", "0.257"\], \["28195.70", "0.005"\], \["28195.80", "0.099"\], \["28195.90", "0.035"\], \["28196.00", "2.935"\], \["28196.10", "0.008"\], \["28196.20", "0.012"\], \["28196.30", "0.113"\], \["28196.40", "0.168"\], \["28196.50", "3.003"\], \["28196.60", "0.060"\], \["28196.70", "0.990"\], \["28196.80", "4.424"\], \["28197.00", "0.170"\], \["28197.10", "0.106"\], \["28197.20", "2.753"\], \["28197.30", "3.056"\], \["28197.40", "0.085"\], \["28197.50", "0.001"\], \["28197.60", "0.433"\], \["28197.70", "1.963"\], \["28197.80", "1.724"\], \["28197.90", "0.090"\], \["28198.00", "0.414"\], \["28198.10", "0.083"\], \["28198.20", "0.436"\], \["28198.30", "0.473"\], \["28198.40", "1.423"\], \["28198.50", "0.123"\], \["28198.70", "0.396"\], \["28198.80", "0.186"\], \["28198.90", "0.011"\], \["28199.00", "7.475"\], \["28199.10", "2.453"\], \["28199.20", "0.380"\], \["28199.30", "3.340"\], \["28199.40", "4.688"\], \["28199.50", "0.153"\], \["28199.60", "0.023"\], \["28199.70", "0.315"\], \["28199.90", "0.014"\], \["28200.00", "18.031"\], \["28200.10", "1.041"\], \["28200.20", "0.505"\], \["28200.40", "0.054"\], \["28200.50", "0.084"\], \["28200.60", "1.081"\], \["28200.70", "0.182"\], \["28200.80", "4.406"\], \["28200.90", "0.409"\], \["28201.00", "0.016"\], \["28201.10", "0.414"\], \["28201.20", "1.624"\], \["28201.30", "0.111"\], \["28201.40", "2.080"\], \["28201.50", "0.268"\], \["28201.60", "0.083"\], \["28201.70", "0.081"\], \["28201.80", "0.186"\], \["28201.90", "0.209"\], \["28202.00", "3.496"\], \["28202.10", "0.055"\], \["28202.20", "3.021"\], \["28202.30", "0.433"\], \["28202.40", "0.219"\], \["28202.50", "5.000"\], \["28202.60", "0.001"\], \["28202.70", "0.255"\], \["28202.80", "22.064"\], \["28202.90", "0.084"\], \["28203.00", "11.714"\], \["28203.10", "0.001"\], \["28203.20", "0.092"\], \["28203.30", "0.233"\], \["28203.40", "4.383"\], \["28203.50", "19.015"\], \["28203.60", "3.235"\], \["28203.70", "0.269"\], \["28203.80", "0.109"\], \["28203.90", "0.296"\], \["28204.00", "0.337"\], \["28204.10", "0.142"\], \["28204.20", "0.087"\], \["28204.30", "0.004"\], \["28204.40", "6.686"\], \["28204.50", "2.388"\], \["28204.60", "0.426"\], \["28204.70", "0.444"\], \["28204.80", "1.497"\], \["28204.90", "8.441"\], \["28205.00", "0.627"\], \["28205.10", "4.597"\], \["28205.20", "0.001"\], \["28205.30", "0.011"\], \["28205.40", "0.064"\], \["28205.50", "0.411"\], \["28205.60", "0.295"\], \["28205.70", "0.213"\], \["28205.80", "0.084"\], \["28205.90", "1.043"\], \["28206.00", "0.227"\], \["28206.10", "0.004"\], \["28206.20", "0.406"\], \["28206.30", "1.735"\], \["28206.40", "0.018"\], \["28206.50", "2.383"\], \["28206.70", "0.973"\], \["28206.80", "3.162"\], \["28206.90", "3.793"\], \["28207.00", "10.365"\], \["28207.10", "0.295"\], \["28207.20", "0.085"\], \["28207.30", "0.196"\], \["28207.40", "2.647"\], \["28207.50", "4.286"\], \["28207.70", "0.506"\], \["28207.80", "0.085"\], \["28207.90", "1.995"\], \["28208.00", "3.232"\], \["28208.10", "0.050"\], \["28208.20", "0.105"\], \["28208.30", "0.071"\], \["28208.50", "1.316"\], \["28208.60", "0.085"\], \["28208.70", "0.849"\], \["28208.80", "1.434"\], \["28208.90", "2.712"\], \["28209.00", "9.793"\], \["28209.10", "12.185"\], \["28209.20", "0.720"\], \["28209.40", "9.574"\], \["28209.50", "1.182"\], \["28209.60", "0.145"\], \["28209.70", "0.292"\], \["28209.80", "0.256"\], \["28209.90", "0.120"\], \["28210.00", "1.127"\], \["28210.10", "6.834"\], \["28210.20", "30.180"\], \["28210.30", "1.563"\], \["28210.40", "6.369"\], \["28210.50", "0.081"\], \["28210.60", "0.167"\], \["28210.70", "0.002"\], \["28210.90", "0.091"\], \["28211.00", "1.032"\], \["28211.10", "0.359"\], \["28211.20", "4.299"\], \["28211.30", "2.474"\], \["28211.40", "6.268"\], \["28211.50", "0.200"\], \["28211.60", "0.289"\], \["28211.70", "0.107"\], \["28211.80", "3.086"\], \["28211.90", "1.842"\], \["28212.00", "4.012"\], \["28212.10", "3.379"\], \["28212.20", "2.258"\], \["28212.30", "0.081"\], \["28212.40", "2.738"\], \["28212.50", "0.015"\], \["28212.60", "4.417"\], \["28212.70", "0.080"\], \["28212.80", "4.372"\], \["28212.90", "0.328"\], \["28213.00", "0.075"\], \["28213.10", "0.910"\], \["28213.20", "0.117"\], \["28213.30", "3.360"\], \["28213.40", "0.379"\], \["28213.50", "0.943"\], \["28213.60", "0.564"\], \["28213.70", "0.009"\], \["28213.80", "3.940"\], \["28213.90", "0.024"\], \["28214.00", "4.199"\], \["28214.10", "0.085"\], \["28214.20", "0.012"\], \["28214.30", "0.175"\], \["28214.40", "0.306"\], \["28214.50", "0.742"\], \["28214.60", "0.170"\], \["28214.70", "0.325"\], \["28214.80", "0.080"\], \["28214.90", "0.994"\], \["28215.00", "0.617"\], \["28215.10", "0.001"\], \["28215.20", "0.561"\], \["28215.30", "0.215"\], \["28215.40", "0.007"\], \["28215.50", "3.623"\], \["28215.60", "0.138"\], \["28215.70", "0.020"\], \["28215.80", "0.317"\], \["28215.90", "1.659"\], \["28216.00", "0.628"\], \["28216.10", "1.021"\], \["28216.30", "0.220"\], \["28216.40", "1.549"\], \["28216.50", "0.211"\], \["28216.60", "0.122"\], \["28216.70", "0.238"\], \["28216.80", "5.845"\], \["28216.90", "0.279"\], \["28217.00", "1.474"\], \["28217.20", "0.129"\], \["28217.30", "3.198"\], \["28217.40", "0.237"\], \["28217.50", "6.120"\], \["28217.60", "0.747"\], \["28217.70", "0.385"\], \["28217.80", "0.001"\], \["28217.90", "2.335"\], \["28218.00", "0.679"\], \["28218.10", "0.331"\], \["28218.20", "2.191"\], \["28218.30", "0.764"\], \["28218.40", "0.341"\], \["28218.50", "0.162"\], \["28218.60", "2.987"\], \["28218.70", "0.152"\], \["28218.80", "0.932"\], \["28218.90", "1.451"\], \["28219.00", "0.386"\], \["28219.10", "3.103"\], \["28219.20", "0.115"\], \["28219.30", "0.247"\], \["28219.40", "6.307"\], \["28219.50", "0.122"\], \["28219.60", "0.041"\], \["28219.70", "0.963"\], \["28219.80", "0.129"\], \["28219.90", "4.433"\], \["28220.00", "6.985"\], \["28220.10", "0.460"\], \["28220.20", "0.372"\], \["28220.30", "0.424"\], \["28220.40", "1.460"\], \["28220.50", "1.451"\], \["28220.70", "0.238"\], \["28220.80", "2.689"\], \["28220.90", "0.289"\], \["28221.00", "0.098"\], \["28221.10", "0.011"\], \["28221.20", "0.001"\], \["28221.30", "1.032"\], \["28221.40", "0.845"\], \["28221.50", "0.148"\], \["28221.60", "0.113"\], \["28221.70", "0.359"\], \["28221.80", "0.055"\], \["28222.00", "2.391"\], \["28222.10", "0.159"\], \["28222.20", "0.012"\], \["28222.30", "0.035"\], \["28222.40", "0.252"\], \["28222.50", "1.670"\], \["28222.60", "0.051"\], \["28222.70", "0.090"\], \["28222.80", "0.005"\], \["28222.90", "3.115"\], \["28223.00", "0.941"\], \["28223.10", "0.211"\], \["28223.20", "0.011"\], \["28223.30", "0.086"\], \["28223.40", "3.336"\], \["28223.60", "1.432"\], \["28223.70", "11.014"\], \["28223.80", "0.052"\], \["28223.90", "0.064"\], \["28224.00", "15.905"\], \["28224.10", "0.186"\], \["28224.20", "0.244"\], \["28224.30", "0.492"\], \["28224.40", "0.125"\], \["28224.50", "0.002"\], \["28224.60", "0.135"\], \["28224.70", "0.026"\], \["28224.80", "0.862"\], \["28224.90", "0.138"\], \["28225.00", "2.189"\], \["28225.10", "1.957"\], \["28225.20", "0.005"\], \["28225.30", "0.035"\], \["28225.40", "0.088"\], \["28225.50", "0.305"\], \["28225.70", "0.191"\], \["28225.80", "0.001"\], \["28225.90", "2.999"\], \["28226.00", "0.361"\], \["28226.10", "0.084"\], \["28226.20", "0.182"\], \["28226.30", "11.900"\], \["28226.40", "0.570"\], \["28226.50", "1.386"\], \["28226.60", "0.002"\], \["28226.70", "0.059"\], \["28226.80", "0.118"\], \["28226.90", "0.115"\], \["28227.00", "0.181"\], \["28227.10", "0.004"\], \["28227.20", "0.221"\], \["28227.30", "0.320"\], \["28227.40", "0.163"\], \["28227.50", "0.080"\], \["28227.60", "0.630"\], \["28227.80", "0.065"\], \["28227.90", "0.279"\], \["28228.00", "2.303"\], \["28228.10", "0.380"\], \["28228.20", "2.645"\], \["28228.40", "6.630"\], \["28228.50", "0.386"\], \["28228.60", "0.370"\], \["28228.70", "0.336"\], \["28228.80", "0.005"\], \["28228.90", "0.159"\], \["28229.00", "28.807"\], \["28229.10", "0.001"\], \["28229.20", "0.004"\], \["28229.30", "0.187"\], \["28229.40", "0.001"\], \["28229.50", "0.901"\], \["28229.60", "1.410"\], \["28229.70", "0.083"\], \["28229.80", "1.477"\], \["28229.90", "1.876"\], \["28230.00", "6.240"\], \["28230.10", "0.311"\], \["28230.20", "2.843"\], \["28230.30", "0.207"\], \["28230.40", "5.485"\], \["28230.50", "0.208"\], \["28230.60", "3.067"\], \["28230.70", "0.235"\], \["28230.80", "0.092"\], \["28230.90", "0.117"\], \["28231.00", "0.041"\], \["28231.10", "0.021"\], \["28231.20", "0.002"\], \["28231.30", "0.006"\], \["28231.40", "1.011"\], \["28231.50", "0.014"\], \["28231.70", "0.252"\], \["28231.80", "2.973"\], \["28231.90", "0.007"\], \["28232.00", "1.155"\], \["28232.10", "0.110"\], \["28232.20", "0.135"\], \["28232.30", "0.050"\], \["28232.40", "0.149"\], \["28232.50", "0.270"\], \["28232.60", "0.203"\], \["28232.70", "0.015"\], \["28232.80", "0.197"\], \["28232.90", "0.145"\], \["28233.00", "0.074"\], \["28233.20", "0.122"\], \["28233.30", "2.320"\], \["28233.50", "0.137"\], \["28233.60", "0.152"\], \["28233.80", "0.196"\], \["28233.90", "0.905"\], \["28234.00", "3.679"\], \["28234.10", "0.169"\], \["28234.20", "0.084"\], \["28234.30", "0.488"\], \["28234.40", "0.126"\], \["28234.50", "0.209"\], \["28234.60", "0.596"\], \["28234.70", "0.357"\], \["28234.80", "2.721"\], \["28234.90", "0.150"\], \["28235.00", "3.057"\], \["28235.10", "2.129"\], \["28235.20", "0.118"\], \["28235.40", "0.165"\], \["28235.50", "0.139"\], \["28235.60", "0.422"\], \["28235.70", "0.329"\], \["28235.80", "0.024"\], \["28235.90", "0.085"\], \["28236.00", "2.251"\], \["28236.10", "0.354"\], \["28236.20", "0.076"\], \["28236.30", "0.099"\], \["28236.40", "0.221"\], \["28236.50", "8.147"\], \["28236.60", "0.166"\], \["28236.70", "0.313"\], \["28236.90", "1.496"\], \["28237.00", "0.084"\], \["28237.10", "0.001"\], \["28237.20", "0.074"\], \["28237.30", "0.096"\], \["28237.40", "0.102"\], \["28237.50", "5.147"\], \["28237.60", "0.066"\], \["28237.70", "0.253"\], \["28237.90", "0.099"\], \["28238.00", "0.046"\], \["28238.10", "0.108"\], \["28238.20", "1.551"\], \["28238.30", "0.286"\], \["28238.40", "5.336"\], \["28238.50", "0.663"\], \["28238.60", "0.009"\], \["28238.70", "1.416"\], \["28238.80", "2.560"\], \["28238.90", "0.233"\], \["28239.00", "2.213"\], \["28239.10", "0.408"\], \["28239.20", "0.281"\], \["28239.30", "0.195"\], \["28239.40", "0.162"\], \["28239.60", "0.188"\], \["28239.70", "0.002"\], \["28239.80", "0.367"\], \["28239.90", "6.932"\], \["28240.00", "13.593"\], \["28240.10", "0.036"\], \["28240.20", "0.118"\], \["28240.30", "0.009"\], \["28240.40", "10.040"\], \["28240.50", "12.444"\], \["28240.60", "3.424"\], \["28240.70", "2.113"\], \["28240.90", "0.362"\], \["28241.00", "0.404"\], \["28241.20", "0.086"\], \["28241.30", "0.093"\], \["28241.40", "3.048"\], \["28241.60", "0.174"\], \["28241.70", "0.296"\], \["28241.80", "0.155"\], \["28241.90", "0.741"\], \["28242.00", "0.040"\], \["28242.10", "0.094"\], \["28242.20", "0.010"\], \["28242.30", "0.188"\], \["28242.40", "0.171"\], \["28242.50", "0.067"\], \["28242.60", "0.144"\], \["28242.70", "0.231"\], \["28242.80", "0.001"\], \["28242.90", "3.156"\], \["28243.00", "0.586"\], \["28243.10", "8.162"\], \["28243.20", "0.030"\], \["28243.30", "0.224"\], \["28243.40", "3.938"\], \["28243.50", "12.026"\], \["28243.60", "0.002"\], \["28243.70", "0.214"\], \["28243.80", "21.460"\], \["28243.90", "0.196"\], \["28244.00", "0.652"\], \["28244.10", "94.981"\], \["28244.20", "0.427"\], \["28244.30", "0.285"\], \["28244.40", "0.180"\], \["28244.50", "2.342"\], \["28244.60", "0.410"\], \["28244.70", "0.003"\], \["28244.80", "0.088"\], \["28244.90", "6.304"\], \["28245.00", "0.848"\], \["28245.10", "0.315"\], \["28245.20", "1.991"\], \["28245.30", "0.366"\], \["28245.40", "0.106"\], \["28245.50", "0.223"\], \["28245.60", "0.223"\], \["28245.70", "0.002"\], \["28245.90", "0.227"\], \["28246.00", "0.316"\], \["28246.10", "0.343"\], \["28246.20", "2.305"\], \["28246.40", "0.021"\], \["28246.50", "0.224"\], \["28246.60", "0.003"\], \["28246.70", "0.025"\], \["28246.80", "0.038"\], \["28246.90", "0.001"\], \["28247.00", "0.486"\], \["28247.10", "0.434"\], \["28247.20", "0.215"\], \["28247.30", "0.119"\], \["28247.40", "0.226"\], \["28247.50", "0.060"\], \["28247.60", "0.225"\], \["28247.70", "0.536"\], \["28247.80", "0.147"\], \["28247.90", "0.080"\], \["28248.00", "1.657"\], \["28248.10", "0.093"\], \["28248.20", "0.317"\], \["28248.30", "0.223"\], \["28248.40", "0.161"\], \["28248.50", "0.954"\], \["28248.60", "0.403"\], \["28248.70", "0.080"\], \["28248.80", "0.005"\], \["28248.90", "0.256"\], \["28249.00", "3.201"\], \["28249.10", "0.309"\], \["28249.20", "0.005"\], \["28249.30", "0.078"\], \["28249.40", "0.268"\], \["28249.50", "4.679"\], \["28249.60", "0.130"\], \["28249.70", "0.210"\], \["28249.80", "15.197"\], \["28249.90", "4.501"\], \["28250.00", "20.080"\], \["28250.10", "0.133"\], \["28250.20", "0.148"\], \["28250.30", "0.106"\], \["28250.40", "0.150"\], \["28250.50", "0.036"\], \["28250.60", "0.044"\], \["28250.70", "0.481"\], \["28250.80", "0.002"\], \["28250.90", "1.351"\], \["28251.00", "5.915"\], \["28251.10", "0.004"\], \["28251.20", "0.176"\], \["28251.30", "0.215"\], \["28251.40", "0.145"\], \["28251.50", "0.202"\], \["28251.60", "0.611"\], \["28251.80", "0.835"\], \["28251.90", "0.288"\], \["28252.00", "0.261"\], \["28252.10", "0.084"\], \["28252.20", "0.005"\], \["28252.30", "0.119"\], \["28252.60", "0.055"\], \["28252.70", "0.005"\], \["28252.80", "0.002"\], \["28252.90", "0.463"\], \["28253.00", "0.070"\], \["28253.10", "0.231"\], \["28253.20", "0.149"\], \["28253.30", "2.858"\], \["28253.40", "1.579"\], \["28253.50", "0.070"\], \["28253.60", "0.103"\], \["28253.70", "0.007"\], \["28253.80", "0.295"\], \["28253.90", "0.001"\], \["28254.00", "0.252"\], \["28254.10", "0.073"\], \["28254.20", "0.020"\], \["28254.30", "0.002"\], \["28254.40", "0.636"\], \["28254.50", "0.171"\], \["28254.60", "0.097"\], \["28254.70", "0.001"\], \["28254.80", "0.007"\], \["28255.00", "3.013"\], \["28255.10", "0.100"\], \["28255.20", "5.642"\], \["28255.30", "0.172"\], \["28255.40", "0.085"\], \["28255.50", "0.131"\]\]}\\n' b'1680652700600379 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1680652700594,"T":1680652700586,"s":"BTCUSDT","U":2710246765373,"u":2710246765597,"pu":2710246765326,"b":\[\["5000.00","14.368"\],\["28018.90","0.000"\],\["28049.60","0.288"\],\["28080.20","3.000"\]\],"a":\[\["28145.20","6.961"\],\["28173.30","0.015"\],\["28220.30","0.424"\]\]}}\\n' b'1680652700600379 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1680652700610,"T":1680652700603,"s":"BTCUSDT","U":2710246765629,"u":2710246765882,"pu":2710246765597,"b":\[\["28018.90","0.080"\],\["28080.20","3.080"\],\["28117.00","0.223"\]\],"a":\[\["28220.20","0.452"\],\["28250.90","1.431"\],\["28281.50","0.081"\]\]}}\\n' b'1680652700618593 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1680652700628,"T":1680652700614,"s":"BTCUSDT","U":2710246765966,"u":2710246766123,"pu":2710246765882,"b":\[\],"a":\[\["28173.30","0.014"\],\["28989.60","0.650"\]\]}}\\n' The first token of the line is timestamp received by local. **Note:** There are currently two different implementations of the feed data collector: one in Python and another in Rust. The Python implementation records timestamps in microseconds, while the Rust implementation records timestamps in nanoseconds. Therefore, Python HftBacktest examples primarily use microseconds, whereas Rust HftBacktest examples use nanoseconds. Be mindful of the timestamp units. The data needs to be converted to normalized data that can be fed into HftBacktest. `convert` method also attempts to correct timestamps by reordering the rows. ### For HftBacktest in Python, use the Python version of the Data Collector[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#For-HftBacktest-in-Python,-use-the-Python-version-of-the-Data-Collector "Permalink to this heading") \[2\]: import numpy as np from hftbacktest.data.utils import binancefutures data \= binancefutures.convert('usdm/btcusdt\_20230404.dat.gz') np.savez\_compressed('btcusdt\_20230404', data\=data) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 18836.0 Correcting the event order You can save the data directly to a file by providing `output_filename`. \[3\]: binancefutures.convert('usdm/btcusdt\_20230405.dat.gz', output\_filename\='btcusdt\_20230405', compress\=True) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 26932.0 Correcting the event order Saving to btcusdt\_20230405 \[3\]: array(\[\[ 1.00000000e+00, 1.68065280e+15, 1.68065280e+15,\ 1.00000000e+00, 2.23000000e+04, 2.78800000e+00\],\ \[ 1.00000000e+00, 1.68065280e+15, 1.68065280e+15,\ 1.00000000e+00, 2.75774000e+04, 0.00000000e+00\],\ \[ 1.00000000e+00, 1.68065280e+15, 1.68065280e+15,\ 1.00000000e+00, 2.80238000e+04, 1.63800000e+00\],\ ...,\ \[ 1.00000000e+00, 1.68065321e+15, 1.68065321e+15,\ -1.00000000e+00, 2.81499000e+04, 1.53200000e+00\],\ \[ 1.00000000e+00, 1.68065321e+15, 1.68065321e+15,\ -1.00000000e+00, 2.85725000e+04, 1.83000000e-01\],\ \[ 1.00000000e+00, 1.68065321e+15, 1.68065321e+15,\ -1.00000000e+00, 2.89844000e+04, 1.00000000e-03\]\]) ### For HftBacktest in Python, use the Rust version of the Data Collector[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#For-HftBacktest-in-Python,-use-the-Rust-version-of-the-Data-Collector "Permalink to this heading") **Note:** The timestamp is in nanoseconds. \[4\]: import numpy as np from hftbacktest.data.utils import binancefutures binancefutures.convert( "SOLUSDT\_20240420.gz", output\_filename\="SOLUSDT\_20240420.npz", compress\=True, timestamp\_unit\="ns", combined\_stream\=False ) Correcting the latency Correcting the event order Saving to SOLUSDT\_20240420.npz \[4\]: array(\[\[ 1.0000000e+00, 1.7135712e+18, 1.7135712e+18, 1.0000000e+00,\ 1.3380900e+02, 1.0000000e+00\],\ \[ 1.0000000e+00, 1.7135712e+18, 1.7135712e+18, 1.0000000e+00,\ 1.3702000e+02, 2.0000000e+00\],\ \[ 1.0000000e+00, 1.7135712e+18, 1.7135712e+18, 1.0000000e+00,\ 1.3739200e+02, 0.0000000e+00\],\ ...,\ \[ 1.0000000e+00, 1.7136576e+18, 1.7136576e+18, -1.0000000e+00,\ 1.5133300e+02, 1.5000000e+01\],\ \[ 1.0000000e+00, 1.7136576e+18, 1.7136576e+18, -1.0000000e+00,\ 1.5133800e+02, 2.0000000e+00\],\ \[ 1.0000000e+00, 1.7136576e+18, 1.7136576e+18, -1.0000000e+00,\ 1.5134900e+02, 2.3000000e+01\]\]) Normalized data as follows. You can find more details on [Data](https://github.com/nkaz001/hftbacktest/wiki/Data) . \[5\]: import pandas as pd df \= pd.DataFrame(data, columns\=\['event', 'exch\_timestamp', 'local\_timestamp', 'side', 'price', 'qty'\]) df\['event'\] \= df\['event'\].astype(int) df\['exch\_timestamp'\] \= df\['exch\_timestamp'\].astype(int) df\['local\_timestamp'\] \= df\['local\_timestamp'\].astype(int) df\['side'\] \= df\['side'\].astype(int) df \[5\]: | | event | exch\_timestamp | local\_timestamp | side | price | qty | | --- | --- | --- | --- | --- | --- | --- | | 0 | 2 | 1680652700452000 | 1680652700460369 | \-1 | 28145.1 | 0.002 | | 1 | 2 | 1680652700452000 | 1680652700460521 | \-1 | 28145.1 | 0.020 | | 2 | 2 | 1680652700452000 | 1680652700460561 | \-1 | 28145.1 | 0.020 | | 3 | 2 | 1680652700452000 | 1680652700461364 | \-1 | 28145.1 | 0.008 | | 4 | 2 | 1680652700462000 | 1680652700473746 | 1 | 28145.2 | 0.002 | | ... | ... | ... | ... | ... | ... | ... | | 71014 | 1 | 1680652799975000 | 1680652799977784 | \-1 | 28182.7 | 0.441 | | 71015 | 1 | 1680652799975000 | 1680652799977784 | \-1 | 28186.9 | 0.054 | | 71016 | 1 | 1680652799975000 | 1680652799977784 | \-1 | 28225.5 | 3.213 | | 71017 | 1 | 1680652799975000 | 1680652799977784 | \-1 | 28231.7 | 0.356 | | 71018 | 1 | 1680652799975000 | 1680652799977784 | \-1 | 28251.4 | 0.262 | 71019 rows × 6 columns ### For HftBacktest in Rust, use the Rust version of the Data Collector[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#For-HftBacktest-in-Rust,-use-the-Rust-version-of-the-Data-Collector "Permalink to this heading") \[6\]: from hftbacktest.data.utils import binancefutures binancefutures.convert( "SOLUSDT\_20240420.gz", output\_filename\="SOLUSDT\_20240420.npz", compress\=True, timestamp\_unit\="ns", combined\_stream\=False, structured\_array\=True ) Correcting the latency Correcting the event order Saving to SOLUSDT\_20240420.npz \[6\]: array(\[(3758096385, 1713571200043000064, 1713571200045828864, 133.809, 1.),\ (3758096385, 1713571200043000064, 1713571200045828864, 137.02 , 2.),\ (3758096385, 1713571200043000064, 1713571200045828864, 137.392, 0.),\ ...,\ (3489660929, 1713657599968000000, 1713657599976203008, 151.333, 15.),\ (3489660929, 1713657599968000000, 1713657599976203008, 151.338, 2.),\ (3489660929, 1713657599968000000, 1713657599976203008, 151.349, 23.)\], dtype=\[('ev', '\] 2.95M 6.02MB/s in 0.5s 2024-05-19 09:39:06 (6.02 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-05-19 09:39:07-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 172.64.147.51, 104.18.40.205, 2606:4700:4400::6812:28cd, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|172.64.147.51|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 17.6MB/s in 12s 2024-05-19 09:39:20 (19.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] ### For HftBacktest in Python[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#id1 "Permalink to this heading") \[12\]: from hftbacktest.data.utils import tardis data \= tardis.convert(\['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\]) np.savez\_compressed('btcusdt\_20200201.npz', data\=data) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Merging Correcting the latency Correcting the event order You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000, compress\=True ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Merging Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz \[13\]: array(\[\[ 2.0000000e+00, 1.5805152e+15, 1.5805152e+15, 1.0000000e+00,\ 9.3645100e+03, 1.1970000e+00\],\ \[ 2.0000000e+00, 1.5805152e+15, 1.5805152e+15, 1.0000000e+00,\ 9.3656700e+03, 2.0000000e-02\],\ \[ 2.0000000e+00, 1.5805152e+15, 1.5805152e+15, 1.0000000e+00,\ 9.3658600e+03, 1.0000000e-02\],\ ...,\ \[ 1.0000000e+00, 1.5806016e+15, 1.5806016e+15, 1.0000000e+00,\ 9.3514700e+03, 3.9140000e+00\],\ \[ 1.0000000e+00, 1.5806016e+15, 1.5806016e+15, -1.0000000e+00,\ 9.3977800e+03, 1.0000000e-01\],\ \[ 1.0000000e+00, 1.5806016e+15, 1.5806016e+15, 1.0000000e+00,\ 9.3481400e+03, 3.9800000e+00\]\]) You can also build the snapshot in the same way as described above. ### For HftBacktest in Rust[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Data%20Preparation.html#id2 "Permalink to this heading") \[14\]: tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000, compress\=True, structured\_array\=True, timestamp\_unit\='ns' ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Merging Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz \[14\]: array(\[(3758096386, 1580515202342000128, 1580515202497051904, 9364.51, 1.197),\ (3758096386, 1580515202342000128, 1580515202497346048, 9365.67, 0.02 ),\ (3758096386, 1580515202342000128, 1580515202497351936, 9365.86, 0.01 ),\ ...,\ (3758096385, 1580601599836000000, 1580601599962960896, 9351.47, 3.914),\ (3489660929, 1580601599836000000, 1580601599963461120, 9397.78, 0.1 ),\ (3758096385, 1580601599848000000, 1580601599973647104, 9348.14, 3.98 )\], dtype=\[('ev', '= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception return arrival\_depth\[:t\], mid\_price\_chg\[:t\] Since we’re not considering the order’s queue position when measuring trading intensity, only market trades that cross our quote will be counted as executed. \[2\]: @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] Run HftBacktest to replay the market and record order arrival depth and price changes. \[3\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) arrival\_depth, mid\_price\_chg \= measure\_trading\_intensity\_and\_volatility(hbt) \_ \= hbt.close() Measure trading intensity from the recorded order arrival depth and plot it. \[4\]: tmp \= np.zeros(500, np.float64) \# Measures trading intensity (lambda) for the first 10-minute window. lambda\_ \= measure\_trading\_intensity(arrival\_depth\[:6\_000\], tmp) \# Since it is measured for a 10-minute window, divide by 600 to convert it to per second. lambda\_ /= 600 \# Creates ticks from the mid-price. ticks \= np.arange(len(lambda\_)) + .5 \[5\]: from matplotlib import pyplot as plt plt.plot(ticks, lambda\_) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') \[5\]: Text(0, 0.5, 'Count (per second)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png) Calibrate \\(A\\) and \\(k\\) using linear regression, since by taking the logarithm of both sides of lambda, it becomes \\(log \\lambda = -k \\delta + logA\\). \[6\]: @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept \[7\]: y \= np.log(lambda\_) k\_, logA \= linear\_regression(ticks, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=0.8426573649994981, k=0.016958811558646644 \[8\]: plt.plot(lambda\_) plt.plot(A \* np.exp(\-k \* ticks)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[8\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png) As you can see, the fitted lambda function is not accurate across the entire range. More specifically, it overestimates the trading intensity for the shallow range near the mid-price and underestimates it for the deep range away from the mid-price. Since our quotes are likely to be placed in the range close to the mid-price, at least under typical market conditions (excluding high volatility conditions), we will refit the function specifically for the nearest range. \[9\]: \# Refits for the range un to 70 ticks. x\_shallow \= ticks\[:70\] lambda\_shallow \= lambda\_\[:70\] y \= np.log(lambda\_shallow) k\_, logA \= linear\_regression(x\_shallow, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=2.986162360812285, k=0.04235741115084049 \[10\]: plt.plot(lambda\_shallow) plt.plot(A \* np.exp(\-k \* x\_shallow)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[10\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png) Now, we have a more accurate trading intensity function. Let’s see where our quote will be placed. But before we do that, let’s calculate the volatility first. \[11\]: \# Since we need volatility in ticks per square root of a second and our measurement is every 100ms, \# multiply by the square root of 10. volatility \= np.nanstd(mid\_price\_chg) \* np.sqrt(10) print(volatility) 10.725509539115974 Compute \\(c\_1\\) and \\(c\_2\\) according to the equations. \[12\]: @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 In the Guéant–Lehalle–Fernandez-Tapia formula, \\(\\Delta = 1\\) and \\(\\xi = \\gamma\\). the value of \\(\\gamma\\) is arbitrarily chosen. \[13\]: gamma \= 0.05 delta \= 1 volatility \= 10.69 c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= 1 \* c1 + 1 / 2 \* c2 \* volatility skew \= c2 \* volatility print('half\_spread\_tick={}, skew={}'.format(half\_spread\_tick, skew)) half\_spread\_tick=20.47208533844371, skew=9.76326865029227 What does it mean when your quote is positioned 20 ticks away from the mid-price? By analyzing the recorded order arrival depth, you can identify the number of market trades you’ll participate in as a market maker, measured in terms of count instead of volume. Additionally, the skew appears to be quite strong, as accumulating just two positions offsets the entire half spread. \[14\]: from scipy import stats \# inverse of percentile pct \= stats.percentileofscore(arrival\_depth\[np.isfinite(arrival\_depth)\], half\_spread\_tick) your\_pct \= 100 \- pct print('{:.2f}%'.format(your\_pct)) 1.86% Approximately 1.86% of market trades per given time-step could execute your quote. Be aware that it’s not the percentage of the traded quantity. Implement a Market Maker using the Model[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Implement-a-Market-Maker-using-the-Model "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. In this example, we will disregard the forecast term and assume that the fair price is equal to the mid price, as we can expect the intrinsic value to remain stable in the short term. \[15\]: from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT out\_dtype \= np.dtype(\[\ ('half\_spread\_tick', 'f8'),\ ('skew', 'f8'),\ ('volatility', 'f8'),\ ('A', 'f8'),\ ('k', 'f8')\ \]) @njit def glft\_market\_maker(hbt, recorder): tick\_size \= hbt.depth(0).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.zeros(10\_000\_000, out\_dtype) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(0): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(0) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) position \= hbt.position(0) orders \= hbt.orders(0) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Cancel orders if they differ from the updated bid and ask prices. order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.price != bid\_price) or (order.side \== SELL and order.price != ask\_price) ): hbt.cancel(0, order.order\_id, False) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / tick\_size) if bid\_price\_as\_order\_id not in orders: hbt.submit\_buy\_order(0, bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX, LIMIT, False) if position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / tick\_size) if ask\_price\_as\_order\_id not in orders: hbt.submit\_sell\_order(0, ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t\].half\_spread\_tick \= half\_spread\_tick out\[t\].skew \= skew out\[t\].volatility \= volatility out\[t\].A \= A out\[t\].k \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[16\]: from hftbacktest import Recorder from hftbacktest.stats import LinearAssetRecord asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[16\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-03 23:59:50 | \-246.379582 | \-264.130529 | \-0.020574 | 0.020601 | 13579.57171 | 590.242857 | \-0.998715 | \-0.000035 | 19790.625 | \[17\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_31_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_31_0.png) \[18\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_32_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_32_0.png) Adjustment factors[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Adjustment-factors "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It looks like the skew is too strong, which is why the market maker is hesitant to take on the position. To alleviate the skew, you can introduce adjustment factors, \\(adj\_1\\) and \\(adj\_2\\), to the calculated half spread and skew, as follow. \\\[\\begin{split}\\text{half spread}\_{adj} = \\text{half spread} \\times adj\_1 \\\\ \\text{skew}\_{adj} = \\text{skew} \\times adj\_2\\end{split}\\\] \[19\]: from numba.typed import Dict @njit def glft\_market\_maker(hbt, recorder): tick\_size \= hbt.depth(0).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.zeros(10\_000\_000, out\_dtype) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 \# Uses the same value as gamma. order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(0): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(0) hbt.clear\_inactive\_orders(0) depth \= hbt.depth(0) position \= hbt.position(0) orders \= hbt.orders(0) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Cancel orders if they differ from the updated bid and ask prices. order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.price\_tick != bid\_price\_tick) or (order.side \== SELL and order.price\_tick != ask\_price\_tick) ): hbt.cancel(0, order.order\_id, False) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / tick\_size) if bid\_price\_as\_order\_id not in orders: hbt.submit\_buy\_order(0, bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX, LIMIT, False) if position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / tick\_size) if ask\_price\_as\_order\_id not in orders: hbt.submit\_sell\_order(0, ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t\].half\_spread\_tick \= half\_spread\_tick out\[t\].skew \= skew out\[t\].volatility \= volatility out\[t\].A \= A out\[t\].k \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[20\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[20\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-03 23:59:50 | 1.202048 | 1.471295 | 0.000359 | 0.004763 | 10987.271675 | 477.498424 | 0.075478 | 7.5295e-7 | 27563.655 | \[21\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_0.png) Improved, but even when accounting for rebates, it can only achieve breakeven at best. As shown below, both the half spread and skew move together, primarily influenced by the \\(c\_2\\) and the market volatility. \[22\]: import polars as pl records \= recorder.get(0) df \= pl.DataFrame(out).with\_columns( pl.Series('timestamp', records\['timestamp'\]), pl.Series('price', records\['price'\]) ).with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns') ) df \= df.group\_by\_dynamic( 'timestamp', every\='5m' ).agg( pl.col('price').last(), pl.col('half\_spread\_tick').last(), pl.col('skew').last(), pl.col('volatility').last(), pl.col('A').last(), pl.col('k').last(), ) fig, (ax1, ax2) \= plt.subplots(2, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 6) ax1.plot(df\['timestamp'\], df\['half\_spread\_tick'\]) ax1.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax1.set\_ylabel('Half spread (tick)') ax2.plot(df\['timestamp'\], df\['skew'\]) ax2.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax2.set\_ylabel('Skew (tick)') \[22\]: Text(0, 0.5, 'Skew (tick)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png) \[23\]: fig, (ax1, ax2, ax3) \= plt.subplots(3, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 9) ax1.plot(df\['timestamp'\], df\['volatility'\]) ax1.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax1.set\_ylabel('Volatility ($ tick/s^{1/2} $)') ax2.plot(df\['timestamp'\], df\['A'\]) ax2.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax2.set\_ylabel('A ($ s^{-1} $)') ax3.plot(df\['timestamp'\], df\['k'\]) ax3.twinx().plot(df\['timestamp'\], df\['price'\], 'r') ax3.set\_ylabel('k ($ tick^{-1} $)') \[23\]: Text(0, 0.5, 'k ($ tick^{-1} $)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_39_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_39_1.png) In the 5-day backtest, it’s evident that profits are generated through rebates, as a result of maintaining high trading volume by consistently posting quotes. \[24\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= glft\_market\_maker(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[24\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 16.282366 | 20.682178 | 0.031145 | 0.009818 | 9463.81907 | 422.448163 | 3.172133 | 0.000015 | 34458.375 | \[25\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_42_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_42_0.png) Integrating Grid Trading[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Integrating-Grid-Trading "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Creating a grid from the bid and ask prices derived from the Guéant–Lehalle–Fernandez-Tapia market making model. \[26\]: from numba.typed import Dict from numba import uint64 @njit def gridtrading\_glft\_mm(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 order\_qty \= 1 max\_position \= 20 grid\_num \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= np.minimum(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= np.maximum(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) return out\[:t\] \[27\]: asset \= ( BacktestAsset() .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20221003.npz',\ 'latency/feed\_latency\_20221004.npz',\ 'latency/feed\_latency\_20221005.npz',\ 'latency/feed\_latency\_20221006.npz',\ 'latency/feed\_latency\_20221007.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(3000.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= gridtrading\_glft\_mm(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=30\_000) stats.summary() \[27\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2022-10-03 00:00:00 | 2022-10-07 23:59:50 | 19.774661 | 24.630456 | 0.055856 | 0.007438 | 5878.736082 | 262.524795 | 7.509437 | 0.000043 | 30859.215 | \[28\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_46_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_46_0.png) You can see it works even better with other coins as well. In the next example, we will show how to create multiple markets to achieve better risk-adjusted returns. \[29\]: asset \= ( BacktestAsset() .data(\[\ 'data/ltcusdt\_20230701.npz',\ 'data/ltcusdt\_20230702.npz',\ 'data/ltcusdt\_20230703.npz',\ 'data/ltcusdt\_20230704.npz',\ 'data/ltcusdt\_20230705.npz'\ \]) .initial\_snapshot('data/ltcusdt\_20230630\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(\[\ 'latency/feed\_latency\_20230701.npz',\ 'latency/feed\_latency\_20230702.npz',\ 'latency/feed\_latency\_20230703.npz',\ 'latency/feed\_latency\_20230704.npz',\ 'latency/feed\_latency\_20230705.npz'\ \]) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(0.0) .roi\_ub(300.0) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_000) out \= gridtrading\_glft\_mm(hbt, recorder.recorder) hbt.close() stats \= LinearAssetRecord(recorder.get(0)).stats(book\_size\=3000) stats.summary() \[29\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-07-01 00:00:00 | 2023-07-05 23:59:50 | 17.17992 | 23.062973 | 0.122535 | 0.032973 | 3425.879303 | 122.800909 | 3.716196 | 0.0002 | 2930.06 | \[30\]: stats.plot() ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_49_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_49_0.png) Wrapping up[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Wrapping-up "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Thus far, we have illustrated how to apply the model to a real-world example. For a more effective market-making algorithm, consider dividing this model into the following categories: * Half-spread: As shown, the half-spread is a function of trading intensity and market volatility. An exponential function used for trading intensity might not be suitable for the entire range. You could develop a more refined approach to convert trading intensity to half-spread. Additionally, while historical trading intensity and market volatility are utilized here, you could forecast short-term trading intensity and volatility to respond more agilely to changes in market conditions. This might involve strategies that use news, events, liquidity vacuums, and other factors to predict volatility explosions. * Skew: The skew is also a function of trading intensity and market volatility. In this model, only inventory risk is considered, but you can also account for other risks, particularly when making multiple markets. BARRA is a good example of other risks that can be managed similarly. * Fair Value Pricing: In this model, the fair price is equal to the mid-price, however, you need to incorporate forecasts such as the micro-price and fair value pricing through correlated assets to enhance the strategy. * Hedging: Hedging is especially crucial when making multiple markets, as it serves as a valuable tool for managing risks. We will address a few more topics in upcoming examples. References[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#References "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- [Dealing with the Inventory Risk - A solution to the market making problem](https://arxiv.org/abs/1105.3115) [Optimal market making](https://arxiv.org/abs/1605.01862) Knight Capital Group [Stochastic Control Theory and High Frequency Trading](https://ieor.columbia.edu/files/seasdepts/industrial-engineering-operations-research/pdf-files/Borden_D_FESeminar_Sp10.pdf) BitMEX Market Making Series [Algo Trading & Market Making](https://blog.bitmex.com/wp-content/uploads/2019/11/Algo-Trading-and-Market-Making.pdf) [How to Market Make Bitcoin Derivatives Lesson 1](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-1/) [How to Market Make Bitcoin Derivatives Lesson 2](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-2/) --- # Order Fill — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Order Fill * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/order_fill.rst.txt) * * * Order Fill[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#order-fill "Link to this heading") ================================================================================================================= Exchange Models[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#exchange-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------------- HftBacktest is a market-data replay-based backtesting tool, which means your order cannot make any changes to the simulated market, no market impact is considered. Therefore, one of the most important assumptions is that your order is small enough not to make any market impact. In the end, you must test it in a live market with real market participants and adjust your backtesting based on the discrepancies between the backtesting results and the live outcomes. Hftbacktest offers two types of exchange simulation. [NoPartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#order-fill-no-partial-fill-exchange) is the default exchange simulation where no partial fills occur. [PartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#order-fill-partial-fill-exchange) is the extended exchange simulation that accounts for partial fills in specific cases. Since the market-data replay-based backtesting cannot alter the market, some partial fill cases may still be unrealistic, such as taking market liquidity. This is because even if your order takes market liquidity, the replayed market data’s market depth and trades cannot change. It is essential to understand the underlying assumptions in each backtesting simulation. ### NoPartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#nopartialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#conditions-for-full-execution "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price * Your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price * Your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#liquidity-taking-order "Link to this heading") > Regardless of the quantity at the best, liquidity-taking orders will be fully executed at the best. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [NoPartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.NoPartialFillExchange.html) and [`no_partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange "hftbacktest.BacktestAsset.no_partial_fill_exchange") ### PartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#partialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#id2 "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price #### Conditions for Partial Execution[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#conditions-for-partial-execution "Link to this heading") Buy order in the order book * Filled by (remaining) sell trade quantity: your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Filled by (remaining) buy trade quantity: your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#id3 "Link to this heading") > Liquidity-taking orders will be executed based on the quantity of the order book, even though the best price and quantity do not change due to your execution. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [PartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.PartialFillExchange.html) and [`partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange "hftbacktest.BacktestAsset.partial_fill_exchange") Queue Models[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#queue-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------- Knowing your order’s queue position is important to achieve accurate order fill simulation in backtesting depending on the liquidity of an order book and trading activities. If an exchange doesn’t provide Market-By-Order, you have to guess it by modeling. HftBacktest currently only supports Market-By-Price that is most crypto exchanges provide and it provides the following queue position models for order fill simulation. Please refer to the details at Models . ![_images/liquidity-and-trade-activities.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/liquidity-and-trade-activities.png) ### RiskAverseQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#riskaversequeuemodel "Link to this heading") This model is the most conservative model in terms of the chance of fill in the queue. The decrease in quantity by cancellation or modification in the order book happens only at the tail of the queue so your order queue position doesn’t change. The order queue position will be advanced only if a trade happens at the price. You can find details below. * [RiskAdverseQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.RiskAdverseQueueModel.html) and [`risk_adverse_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model "hftbacktest.BacktestAsset.risk_adverse_queue_model") ### ProbQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#probqueuemodel "Link to this heading") Based on a probability model according to your current queue position, the decrease in quantity happens at both before and after the queue position. So your queue position is also advanced according to the probability. This model is implemented as described in * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) * [https://rigtorp.se/2013/06/08/estimating-order-queue-position.html](https://rigtorp.se/2013/06/08/estimating-order-queue-position.html) You can find details below. * [ProbQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ProbQueueModel.html) * [PowerProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc.html) and [`power_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model "hftbacktest.BacktestAsset.power_prob_queue_model") * [PowerProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc2.html) and [`power_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2 "hftbacktest.BacktestAsset.power_prob_queue_model2") * [PowerProbQueueFunc3](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc3.html) and [`power_prob_queue_model3`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3 "hftbacktest.BacktestAsset.power_prob_queue_model3") * [LogProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc.html) and [`log_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model "hftbacktest.BacktestAsset.log_prob_queue_model") * [LogProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc2.html) and [`log_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2 "hftbacktest.BacktestAsset.log_prob_queue_model2") By default, three variations are provided. These three models have different probability profiles. ![_images/probqueuemodel.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/probqueuemodel.png) The function f = log(1 + x) exhibits a different probability profile depending on the total quantity at the price level, unlike power functions. ![_images/probqueuemodel_log.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/probqueuemodel_log.png) ![_images/probqueuemodel2.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/probqueuemodel2.png) ![_images/probqueuemodel3.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/probqueuemodel3.png) When you set the function f, it should be as follows. * The probability at 0 should be 0 because if the order is at the head of the queue, all decreases should happen after the order. * The probability at 1 should be 1 because if the order is at the tail of the queue, all decreases should happen before the order. You can see the comparison of the models [here](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Probability%20Queue%20Models.html) . ### Implement a custom queue model[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#implement-a-custom-queue-model "Link to this heading") You need to implement the following traits in Rust based on your usage requirements. * [QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.QueueModel.html) * [L3QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.L3QueueModel.html) Please refer to [the queue model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/queue.rs) . References[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/order_fill.html#references "Link to this heading") ----------------------------------------------------------------------------------------------------------------- This is initially implemented as described in the following articles. * [http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf](http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf) * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) --- # Constants — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Constants * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/reference/constants.rst.txt) * * * Constants[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#constants "Link to this heading") ======================================================================================================================== EXCH\_EVENT _\= 2147483648_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the exchange processor at the exchange timestamp. LOCAL\_EVENT _\= 1073741824_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the local processor at the local timestamp. BUY\_EVENT _\= 536870912_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.BUY_EVENT "Link to this definition") Indicates a buy, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means a bid-side event, while when combined with a trade event, it means that the trade initiator is a buyer. SELL\_EVENT _\= 268435456_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.SELL_EVENT "Link to this definition") Indicates a sell, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means an ask-side event, while when combined with a trade event, it means that the trade initiator is a seller. MARKET _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.MARKET "Link to this definition") MARKET LIMIT _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.LIMIT "Link to this definition") LIMIT BUY _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.BUY "Link to this definition") In the market depth event, this indicates the bid side; in the market trade event, it indicates that the trade initiator is a buyer. SELL _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.SELL "Link to this definition") In the market depth event, this indicates the ask side; in the market trade event, it indicates that the trade initiator is a seller. NONE _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.NONE "Link to this definition") NONE NEW _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.NEW "Link to this definition") NEW EXPIRED _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.EXPIRED "Link to this definition") EXPIRED FILLED _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.FILLED "Link to this definition") FILLED PARTIALLY\_FILLED _\= 5_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "Link to this definition") PARTIALLY\_FILLED CANCELED _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.CANCELED "Link to this definition") CANCELED REJECTED _\= 6_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.REJECTED "Link to this definition") REJECTED GTC _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.GTC "Link to this definition") Good ‘till cancel GTX _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.GTX "Link to this definition") Post only FOK _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.FOK "Link to this definition") Fill or kill IOC _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.order.IOC "Link to this definition") Immediate or cancel ALL\_ASSETS _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.ALL_ASSETS "Link to this definition") Indicates all assets. DEPTH\_EVENT _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT "Link to this definition") Indicates that the market depth is changed. TRADE\_EVENT _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.TRADE_EVENT "Link to this definition") Indicates that a trade occurs in the market. DEPTH\_CLEAR\_EVENT _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT "Link to this definition") Indicates that the market depth is cleared. DEPTH\_SNAPSHOT\_EVENT _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT "Link to this definition") Indicates that the market depth snapshot is received. UNTIL\_END\_OF\_DATA _\= 9223372036854775807_[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA "Link to this definition") Indicates that one should continue until the end of the data. --- # Data Preparation — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Data Preparation * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/tutorials/Data%20Preparation.ipynb.txt) * * * Data Preparation[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Data%20Preparation.html#Data-Preparation "Link to this heading") =============================================================================================================================================== To fully utilize the power of HftBacktest, it requires to input Tick-by-Tick full order book and trade feed data. Unfortunately, free Tick-by-Tick full order book and trade feed data for HFT is not available unlike daily bar data provided by platforms like Yahoo Finance. However, in the case of cryptocurrency, you can collect the full raw feed yourself. Getting started from Binance Futures’ raw feed data[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Data%20Preparation.html#Getting-started-from-Binance-Futures'-raw-feed-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can collect Binance Futures feed yourself using [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) . \[1\]: import gzip with gzip.open('usdm/btcusdt\_20240808.gz', 'r') as f: for i in range(5): line \= f.readline() print(line) b'1723161255030314667 {"stream":"btcusdt@depth@0ms","data":{"e":"depthUpdate","E":1723161256299,"T":1723161256298,"s":"BTCUSDT","U":5123107832006,"u":5123107837557,"pu":5123107831937,"b":\[\["58710.20","0.014"\],\["61496.50","0.010"\],\["61510.90","0.000"\],\["61641.50","1.211"\],\["61652.80","0.195"\],\["61653.30","0.072"\],\["61653.70","0.067"\],\["61657.90","0.067"\],\["61668.50","0.086"\],\["61670.60","0.161"\],\["61672.50","0.821"\],\["61673.60","0.048"\],\["61675.60","0.050"\],\["61684.50","0.765"\],\["61686.20","0.008"\],\["61701.80","0.331"\],\["61703.10","0.238"\],\["61715.90","0.308"\],\["61721.60","0.235"\],\["61724.10","0.002"\],\["61737.00","0.015"\],\["61739.00","0.000"\],\["61740.10","0.008"\],\["61740.50","12.111"\],\["61756.90","0.550"\],\["61758.70","0.003"\],\["61763.20","0.014"\],\["61764.10","0.168"\],\["61764.30","0.000"\],\["61765.50","0.000"\],\["61767.40","0.004"\],\["61768.20","0.120"\],\["61768.60","0.020"\],\["61768.90","0.099"\],\["61770.80","0.049"\],\["61771.10","0.612"\],\["61771.70","0.010"\],\["61773.50","0.035"\],\["61773.80","0.025"\],\["61774.00","0.112"\],\["61775.60","0.010"\],\["61776.00","0.084"\],\["61778.30","0.000"\],\["61778.60","0.408"\],\["61779.30","0.020"\],\["61779.60","0.220"\],\["61783.80","0.002"\],\["61784.90","0.102"\],\["61785.00","0.000"\],\["61788.10","0.140"\],\["61789.50","0.000"\],\["61798.70","0.153"\],\["61800.20","2.507"\]\],"a":\[\["61800.30","3.330"\],\["61804.60","0.057"\],\["61810.00","0.285"\],\["61812.00","0.732"\],\["61814.90","0.000"\],\["61817.20","0.000"\],\["61818.70","0.040"\],\["61824.00","0.860"\],\["61829.10","0.185"\],\["61831.30","0.008"\],\["61831.40","0.501"\],\["61839.00","0.002"\],\["61840.00","0.192"\],\["61856.30","0.003"\],\["61857.10","0.027"\],\["61857.40","0.000"\],\["61858.80","0.005"\],\["61858.90","0.032"\],\["61859.60","0.034"\],\["61874.80","0.006"\],\["61893.40","0.335"\],\["61911.90","0.014"\],\["61925.90","0.000"\],\["61930.50","0.015"\],\["61945.10","0.000"\],\["61953.70","0.000"\],\["62144.00","0.006"\],\["63113.70","0.000"\],\["65880.70","15.918"\]\]}}\\n' b'1723161255088169167 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107839020,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.510","T":1723161256313,"E":1723161256313}}\\n' b'1723161255088176367 {"stream":"btcusdt@trade","data":{"e":"trade","E":1723161256322,"T":1723161256322,"s":"BTCUSDT","t":5266583935,"p":"61800.30","q":"0.006","X":"MARKET","m":false}}\\n' b'1723161255088181667 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840008,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.504","T":1723161256322,"E":1723161256322}}\\n' b'1723161255088182467 {"stream":"btcusdt@bookTicker","data":{"e":"bookTicker","u":5123107840016,"s":"BTCUSDT","b":"61800.20","B":"2.507","a":"61800.30","A":"2.522","T":1723161256322,"E":1723161256322}}\\n' The first token of the line is timestamp received by local. **Note:** The timestamp is in nanoseconds. The data needs to be converted to normalized data that can be fed into HftBacktest. `convert` method also attempts to correct timestamps by reordering the rows. \[2\]: import numpy as np from hftbacktest.data.utils import binancefutures data \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Normalized data as follows. You can find more details on [Data](https://github.com/nkaz001/hftbacktest/wiki/Data) . \[3\]: import polars as pl pl.DataFrame(data) \[3\]: shape: (491\_973, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 58710.2 | 0.014 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61496.5 | 0.01 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61510.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61641.5 | 1.211 | 0 | 0 | 0.0 | | 3758096385 | 1723161256298000000 | 1723161256302471518 | 61652.8 | 0.195 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1723161600030000000 | 1723161600043617932 | 62292.9 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1723161600319000000 | 1723161600370793433 | 5000.0 | 2.321 | 0 | 0 | 0.0 | | 3489660929 | 1723161600709000000 | 1723161600760777134 | 61659.8 | 0.981 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61631.7 | 0.283 | 0 | 0 | 0.0 | | 3758096385 | 1723161601054000000 | 1723161601105649435 | 61632.6 | 0.0 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. \[4\]: \_ \= binancefutures.convert( 'usdm/btcusdt\_20240808.gz', output\_filename\='usdm/btcusdt\_20240808.npz', combined\_stream\=True ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1272156851 Correcting the event order Saving to usdm/btcusdt\_20240808.npz Creating a market depth snapshot[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Data%20Preparation.html#Creating-a-market-depth-snapshot "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As Binance Futures exchange runs 24/7, you need the initial snapshot to get the complete(almost) market depth. [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) fetches the snapshot only when it makes the connection, so you need build the initial snapshot from the start of the collected feed data. \[5\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. data \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001 ) Bid levels are shown before ask levels in the snapshot, and levels are sorted from the best price to the farthest price. \[6\]: pl.DataFrame(data) \[6\]: shape: (9\_597, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096388 | 0 | 0 | 61659.7 | 1.486 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61659.0 | 0.002 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.1 | 0.033 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61658.0 | 6.718 | 0 | 0 | 0.0 | | 3758096388 | 0 | 0 | 61657.9 | 0.007 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660932 | 0 | 0 | 77354.3 | 0.015 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 77905.9 | 0.003 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 80000.0 | 10.708 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 104765.0 | 0.034 | 0 | 0 | 0.0 | | 3489660932 | 0 | 0 | 617050.0 | 0.003 | 0 | 0 | 0.0 | \[7\]: from hftbacktest.data.utils.snapshot import create\_last\_snapshot \# Builds 20240808 End of Day snapshot. It will be used for the initial snapshot for 20240809. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240808.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240808\_eod.npz' ) \[8\]: \# Converts 20240809 data. \_ \= binancefutures.convert( 'usdm/btcusdt\_20240809.gz', output\_filename\='usdm/btcusdt\_20240809.npz', combined\_stream\=True ) \# Builds 20240809's last snapshot. \# Due to the file size limitation of GitHub, btcusdt\_20240809.npz does not contain data for the entire day. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz', initial\_snapshot\='usdm/btcusdt\_20240808\_eod.npz', ) Correcting the latency local\_timestamp is ahead of exch\_timestamp by 1273873720 Correcting the event order Saving to usdm/btcusdt\_20240809.npz \[9\]: \# Builds 20240809's last snapshot without the initial snapshot. \_ \= create\_last\_snapshot( \['usdm/btcusdt\_20240809.npz'\], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last\_wo\_ss.npz' ) \# Builds the 20240809's last snapshot from 20240808 without the initial snapshot. \_ \= create\_last\_snapshot( \[\ 'usdm/btcusdt\_20240808.npz',\ 'usdm/btcusdt\_20240809.npz'\ \], tick\_size\=0.1, lot\_size\=0.001, output\_snapshot\_filename\='usdm/btcusdt\_20240809\_last.npz' ) Getting started from Tardis.dev data[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Data%20Preparation.html#Getting-started-from-Tardis.dev-data "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Few vendors offer tick-by-tick full market depth data along with snapshot and trade data, and Tardis.dev is among them. **Note:** Some data may have an issue with the exchange timestamp. Ideally, the exchange timestamp should reflect the moment the event occurs at the matching engine. However, some data uses the server’s data sent timestamp instead of the matching engine timestamp. \[10\]: \# https://docs.tardis.dev/historical-data-details/binance-futures \# Downloads sample Binance futures BTCUSDT trades !wget https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_trades.csv.gz \# Downloads sample Binance futures BTCUSDT book !wget https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz \-O BTCUSDT\_book.csv.gz \--2024-08-09 09:42:51-- https://datasets.tardis.dev/v1/binance-futures/trades/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.6.96, 104.18.7.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.6.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3090479 (2.9M) \[text/csv\] Saving to: ‘BTCUSDT\_trades.csv.gz’ BTCUSDT\_trades.csv. 100%\[===================>\] 2.95M 5.66MB/s in 0.5s 2024-08-09 09:42:52 (5.66 MB/s) - ‘BTCUSDT\_trades.csv.gz’ saved \[3090479/3090479\] --2024-08-09 09:42:52-- https://datasets.tardis.dev/v1/binance-futures/incremental\_book\_L2/2020/02/01/BTCUSDT.csv.gz Resolving datasets.tardis.dev (datasets.tardis.dev)... 104.18.7.96, 104.18.6.96, 2606:4700::6812:760, ... Connecting to datasets.tardis.dev (datasets.tardis.dev)|104.18.7.96|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 250016849 (238M) \[text/csv\] Saving to: ‘BTCUSDT\_book.csv.gz’ BTCUSDT\_book.csv.gz 100%\[===================>\] 238.43M 9.93MB/s in 23s 2024-08-09 09:43:16 (10.3 MB/s) - ‘BTCUSDT\_book.csv.gz’ saved \[250016849/250016849\] It is recommended to input trade files before depth files. This is because if a depth event occurs due to a trade event, having the trade event before the depth event could provide a more realistic fill during backtesting. However, the sorting process will prioritize events from the first input file when both events have the same timestamp. \[11\]: from hftbacktest.data.utils import tardis data \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\] ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order \[12\]: pl.DataFrame(data) \[12\]: shape: (27\_532\_602, 8) | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | u64 | i64 | i64 | f64 | f64 | u64 | i64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | | 3758096386 | 1580515202342000000 | 1580515202497052000 | 9364.51 | 1.197 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497346000 | 9365.67 | 0.02 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497352000 | 9365.86 | 0.01 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497357000 | 9366.36 | 0.002 | 0 | 0 | 0.0 | | 3758096386 | 1580515202342000000 | 1580515202497363000 | 9366.36 | 0.003 | 0 | 0 | 0.0 | | … | … | … | … | … | … | … | … | | 3489660929 | 1580601599812000000 | 1580601599944404000 | 9397.79 | 0.0 | 0 | 0 | 0.0 | | 3758096385 | 1580601599826000000 | 1580601599952176000 | 9354.8 | 4.07 | 0 | 0 | 0.0 | | 3758096385 | 1580601599836000000 | 1580601599962961000 | 9351.47 | 3.914 | 0 | 0 | 0.0 | | 3489660929 | 1580601599836000000 | 1580601599963461000 | 9397.78 | 0.1 | 0 | 0 | 0.0 | | 3758096385 | 1580601599848000000 | 1580601599973647000 | 9348.14 | 3.98 | 0 | 0 | 0.0 | You can save the data directly to a file by providing `output_filename`. If there are too many rows, you need to increase `buffer_size`. \[13\]: \_ \= tardis.convert( \['BTCUSDT\_trades.csv.gz', 'BTCUSDT\_book.csv.gz'\], output\_filename\='btcusdt\_20200201.npz', buffer\_size\=200\_000\_000 ) Reading BTCUSDT\_trades.csv.gz Reading BTCUSDT\_book.csv.gz Correcting the latency Correcting the event order Saving to btcusdt\_20200201.npz Tardis.dev artificially inserts the SOD snapshot to the start of the daily file. If you continuously backtest multiple days, you don’t need the snapshot every start of days and it may incur more time to backtest. You can choose to include the Tardis.dev’s SOD snapshot in the converted file using the option. --- # Order Latency Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Order Latency Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/tutorials/Order%20Latency%20Data.ipynb.txt) * * * Order Latency Data[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Order%20Latency%20Data.html#Order-Latency-Data "Link to this heading") ======================================================================================================================================================= To obtain more realistic backtesting results, accounting for latencies is crucial. Therefore, it’s important to collect both feed data and order data with timestamps to measure your order latency. The best approach is to gather your own order latencies. You can collect order latency based on your live trading or by regularly submitting orders at a price that cannot be filled and then canceling them for recording purposes. However, if you don’t have access to them or want to establish a target, you will need to artificially generate order latency. You can model this latency based on factors such as feed latency, trade volume, and the number of events. In this guide, we will demonstrate a simple method to generate order latency from feed latency using a multiplier and offset for adjustment. First, loads the feed data. \[1\]: import numpy as np data \= np.load('btcusdt\_20200201.npz')\['data'\] data \[1\]: array(\[(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\ ...,\ (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\ (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\ (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)\], dtype=\[('ev', '= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception return arrival\_depth\[:t\], mid\_price\_chg\[:t\] Since we’re not considering the order’s queue position when measuring trading intensity, only market trades that cross our quote will be counted as executed. \[2\]: @njit def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] Run HftBacktest to replay the market and record order arrival depth and price changes. \[3\]: from hftbacktest import HftBacktest, FeedLatency, Linear hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ \], snapshot\='data/ethusdt\_20221002\_eod.npz', tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, asset\_type\=Linear, order\_latency\=FeedLatency(), trade\_list\_size\=10\_000, ) arrival\_depth, mid\_price\_chg \= measure\_trading\_intensity\_and\_volatility(hbt) Load data/ethusdt\_20221003.npz Measure trading intensity from the recorded order arrival depth and plot it. \[4\]: tmp \= np.zeros(500, np.float64) \# Measures trading intensity (lambda) for the first 10-minute window. lambda\_ \= measure\_trading\_intensity(arrival\_depth\[:6\_000\], tmp) \# Since it is measured for a 10-minute window, divide by 600 to convert it to per second. lambda\_ /= 600 \# Creates ticks from the mid-price. ticks \= np.arange(len(lambda\_)) + .5 \[5\]: from matplotlib import pyplot as plt plt.plot(ticks, lambda\_) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') \[5\]: Text(0, 0.5, 'Count (per second)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_11_1.png) Calibrate \\(A\\) and \\(k\\) using linear regression, since by taking the logarithm of both sides of lambda, it becomes \\(log \\lambda = -k \\delta + logA\\). \[6\]: @njit def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept \[7\]: y \= np.log(lambda\_) k\_, logA \= linear\_regression(ticks, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=0.8793116000410844, k=0.01761086117922129 \[8\]: plt.plot(lambda\_) plt.plot(A \* np.exp(\-k \* ticks)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[8\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_15_1.png) As you can see, the fitted lambda function is not accurate across the entire range. More specifically, it overestimates the trading intensity for the shallow range near the mid-price and underestimates it for the deep range away from the mid-price. Since our quotes are likely to be placed in the range close to the mid-price, at least under typical market conditions (excluding high volatility conditions), we will refit the function specifically for the nearest range. \[9\]: \# Refits for the range un to 70 ticks. x\_shallow \= ticks\[:70\] lambda\_shallow \= lambda\_\[:70\] y \= np.log(lambda\_shallow) k\_, logA \= linear\_regression(x\_shallow, y) A \= np.exp(logA) k \= \-k\_ print('A={}, k={}'.format(A, k)) A=2.9932203436865956, k=0.04249732177397641 \[10\]: plt.plot(lambda\_shallow) plt.plot(A \* np.exp(\-k \* x\_shallow)) plt.xlabel('$ \\delta $ (ticks from the mid-price)') plt.ylabel('Count (per second)') plt.legend(\['Actual', 'Fitted curve'\]) \[10\]: ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_18_1.png) Now, we have a more accurate trading intensity function. Let’s see where our quote will be placed. But before we do that, let’s calculate the volatility first. \[11\]: \# Since we need volatility in ticks per square root of a second and our measurement is every 100ms, \# multiply by the square root of 10. volatility \= np.nanstd(mid\_price\_chg) \* np.sqrt(10) print(volatility) 10.690046868333601 Compute \\(c\_1\\) and \\(c\_2\\) according to the equations. \[12\]: @njit def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 In the Guéant–Lehalle–Fernandez-Tapia formula, \\(\\Delta = 1\\) and \\(\\xi = \\gamma\\). the value of \\(\\gamma\\) is arbitrarily chosen. \[13\]: gamma \= 0.05 delta \= 1 volatility \= 10.69 c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= 1 \* c1 + 1 / 2 \* c2 \* volatility skew \= c2 \* volatility print('half\_spread={}, skew={}'.format(half\_spread, skew)) half\_spread=20.419892817641397, skew=9.7302402975805 What does it mean when your quote is positioned 20 ticks away from the mid-price? By analyzing the recorded order arrival depth, you can identify the number of market trades you’ll participate in as a market maker, measured in terms of count instead of volume. Additionally, the skew appears to be quite strong, as accumulating just two positions offsets the entire half spread. \[14\]: from scipy import stats \# inverse of percentile pct \= stats.percentileofscore(arrival\_depth\[np.isfinite(arrival\_depth)\], half\_spread) your\_pct \= 100 \- pct print('{:.2f}%'.format(your\_pct)) 1.86% Approximately 1.86% of market trades per given time-step could execute your quote. Be aware that it’s not the percentage of the traded quantity. Implement a Market Maker using the Model[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Implement-a-Market-Maker-using-the-Model "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. In this example, we will disregard the forecast term and assume that the fair price is equal to the mid price, as we can expect the intrinsic value to remain stable in the short term. \[15\]: from numba.typed import Dict @njit def glft\_market\_maker(hbt, stat): arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.full((10\_000\_000, 5), np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + .5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility bid\_depth \= half\_spread + skew \* hbt.position ask\_depth \= half\_spread \- skew \* hbt.position bid\_price \= min(np.round(mid\_price\_tick \- bid\_depth), hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(np.round(mid\_price\_tick + ask\_depth), hbt.best\_ask\_tick) \* hbt.tick\_size #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Cancel orders if they differ from the updated bid and ask prices. for order in hbt.orders.values(): if order.side \== BUY and order.cancellable and order.price != bid\_price: hbt.cancel(order.order\_id) if order.side \== SELL and order.cancellable and order.price != ask\_price: hbt.cancel(order.order\_id) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if hbt.position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / hbt.tick\_size) if bid\_price\_as\_order\_id not in hbt.orders: hbt.submit\_buy\_order(bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX) if hbt.position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / hbt.tick\_size) if ask\_price\_as\_order\_id not in hbt.orders: hbt.submit\_sell\_order(ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t, 0\] \= half\_spread out\[t, 1\] \= skew out\[t, 2\] \= volatility out\[t, 3\] \= A out\[t, 4\] \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. stat.record(hbt) return out\[:t\] \[16\]: from hftbacktest import SquareProbQueueModel, Stat, GTX hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, trade\_list\_size\=10\_000, snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) out \= glft\_market\_maker(hbt, stat.recorder) stat.summary(capital\=10\_000) Load data/ethusdt\_20221003.npz =========== Summary =========== Sharpe ratio: -271.1 Sortino ratio: -306.4 Risk return ratio: -365.0 Annualised return: -2213.36 % Max. draw down: 6.06 % The number of trades per day: 6828 Avg. daily trading volume: 6828 Avg. daily trading amount: 8859717 Max leverage: 1.45 Median leverage: 0.00 ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_30_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_30_1.png) Adjustment factors[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Adjustment-factors "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It looks like the skew is too strong, which is why the market maker is hesitant to take on the position. To alleviate the skew, you can introduce adjustment factors, \\(adj\_1\\) and \\(adj\_2\\), to the calculated half spread and skew, as follow. \\\[\\begin{split}\\text{half spread}\_{adj} = \\text{half spread} \\times adj\_1 \\\\ \\text{skew}\_{adj} = \\text{skew} \\times adj\_2\\end{split}\\\] \[17\]: from numba.typed import Dict @njit def glft\_market\_maker(hbt, stat): arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.full((10\_000\_000, 5), np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + .5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 \# Uses the same value as gamma. order\_qty \= 1 max\_position \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 bid\_depth \= half\_spread + skew \* hbt.position ask\_depth \= half\_spread \- skew \* hbt.position bid\_price \= min(np.round(mid\_price\_tick \- bid\_depth), hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(np.round(mid\_price\_tick + ask\_depth), hbt.best\_ask\_tick) \* hbt.tick\_size #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Cancel orders if they differ from the updated bid and ask prices. for order in hbt.orders.values(): if order.side \== BUY and order.cancellable and order.price != bid\_price: hbt.cancel(order.order\_id) if order.side \== SELL and order.cancellable and order.price != ask\_price: hbt.cancel(order.order\_id) \# If the current position is within the maximum position, \# submit the new order only if no order exists at the same price. if hbt.position < max\_position and np.isfinite(bid\_price): bid\_price\_as\_order\_id \= round(bid\_price / hbt.tick\_size) if bid\_price\_as\_order\_id not in hbt.orders: hbt.submit\_buy\_order(bid\_price\_as\_order\_id, bid\_price, order\_qty, GTX) if hbt.position \> \-max\_position and np.isfinite(ask\_price): ask\_price\_as\_order\_id \= round(ask\_price / hbt.tick\_size) if ask\_price\_as\_order\_id not in hbt.orders: hbt.submit\_sell\_order(ask\_price\_as\_order\_id, ask\_price, order\_qty, GTX) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t, 0\] \= half\_spread out\[t, 1\] \= skew out\[t, 2\] \= volatility out\[t, 3\] \= A out\[t, 4\] \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. stat.record(hbt) return out\[:t\] \[18\]: from hftbacktest import SquareProbQueueModel, Stat, GTX hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, trade\_list\_size\=10\_000, snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) out \= glft\_market\_maker(hbt, stat.recorder) stat.summary(capital\=10\_000) Load data/ethusdt\_20221003.npz =========== Summary =========== Sharpe ratio: 5.9 Sortino ratio: 5.6 Risk return ratio: 144.6 Annualised return: 172.58 % Max. draw down: 1.19 % The number of trades per day: 5497 Avg. daily trading volume: 5497 Avg. daily trading amount: 7131834 Max leverage: 3.42 Median leverage: 0.26 ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_33_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_33_1.png) Improved, but even when accounting for rebates, it can only achieve breakeven at best. As shown below, both the half spread and skew move together, primarily influenced by the \\(c\_2\\) and the market volatility. \[19\]: import pandas as pd dt \= stat.datetime() mid \= pd.Series(stat.mid, index\=dt) half\_spread \= pd.Series(out\[:, 0\], index\=dt) skew \= pd.Series(out\[:, 1\], index\=dt) volatility \= pd.Series(out\[:, 2\], index\=dt) A \= pd.Series(out\[:, 3\], index\=dt) k \= pd.Series(out\[:, 4\], index\=dt) fig, axs \= plt.subplots(2, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 6) half\_spread.resample('5min').last().plot(ax\=axs\[0\]) mid.resample('5min').last().plot(ax\=axs\[0\].twinx(), style\='r') axs\[0\].set\_ylabel('Half spread (tick)') skew.resample('5min').last().plot(ax\=axs\[1\]) mid.resample('5min').last().plot(ax\=axs\[1\].twinx(), style\='r') axs\[1\].set\_ylabel('Skew (tick)') \[19\]: Text(0, 0.5, 'Skew (tick)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_35_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_35_1.png) \[20\]: fig, axs \= plt.subplots(3, 1, sharex\=True) fig.subplots\_adjust(hspace\=0) fig.set\_size\_inches(10, 9) volatility.resample('5min').last().plot(ax\=axs\[0\]) mid.resample('5min').last().plot(ax\=axs\[0\].twinx(), style\='r') axs\[0\].set\_ylabel('Volatility ($ tick/s^{1/2} $)') A.resample('5min').last().plot(ax\=axs\[1\]) mid.resample('5min').last().plot(ax\=axs\[1\].twinx(), style\='r') axs\[1\].set\_ylabel('A ($ s^{-1} $)') k.resample('5min').last().plot(ax\=axs\[2\]) mid.resample('5min').last().plot(ax\=axs\[2\].twinx(), style\='r') axs\[2\].set\_ylabel('k ($ tick^{-1} $)') \[20\]: Text(0, 0.5, 'k ($ tick^{-1} $)') ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_36_1.png) In the 5-day backtest, it’s evident that profits are generated through rebates, as a result of maintaining high trading volume by consistently posting quotes. \[21\]: hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, trade\_list\_size\=10\_000, snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) out \= glft\_market\_maker(hbt, stat.recorder) stat.summary(capital\=10\_000) Load data/ethusdt\_20221003.npz Load data/ethusdt\_20221004.npz Load data/ethusdt\_20221005.npz Load data/ethusdt\_20221006.npz Load data/ethusdt\_20221007.npz =========== Summary =========== Sharpe ratio: 17.3 Sortino ratio: 14.0 Risk return ratio: 282.9 Annualised return: 512.09 % Max. draw down: 1.81 % The number of trades per day: 8385 Avg. daily trading volume: 8385 Avg. daily trading amount: 11231937 Max leverage: 5.24 Median leverage: 0.27 ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_38_1.png) Integrating Grid Trading[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Integrating-Grid-Trading "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Creating a grid from the bid and ask prices derived from the Guéant–Lehalle–Fernandez-Tapia market making model. \[22\]: from numba.typed import Dict @njit def gridtrading\_glft\_mm(hbt, stat): arrival\_depth \= np.full(10\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(10\_000\_000, np.nan, np.float64) out \= np.full((10\_000\_000, 5), np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + .5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 adj2 \= 0.05 order\_qty \= 1 max\_position \= 20 grid\_num \= 20 \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 bid\_depth \= half\_spread + skew \* hbt.position ask\_depth \= half\_spread \- skew \* hbt.position bid\_price \= min(mid\_price\_tick \- bid\_depth, hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(mid\_price\_tick + ask\_depth, hbt.best\_ask\_tick) \* hbt.tick\_size grid\_interval \= max(np.round(half\_spread) \* hbt.tick\_size, hbt.tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) #-------------------------------------------------------- \# Records variables and stats for analysis. out\[t, 0\] \= half\_spread out\[t, 1\] \= skew out\[t, 2\] \= volatility out\[t, 3\] \= A out\[t, 4\] \= k t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg) or t \>= len(out): raise Exception \# Records the current state for stat calculation. stat.record(hbt) return out\[:t\] \[23\]: hbt \= HftBacktest( \[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, trade\_list\_size\=10\_000, snapshot\='data/ethusdt\_20221002\_eod.npz' ) stat \= Stat(hbt) out \= gridtrading\_glft\_mm(hbt, stat.recorder) stat.summary(capital\=25\_000) Load data/ethusdt\_20221003.npz Load data/ethusdt\_20221004.npz Load data/ethusdt\_20221005.npz Load data/ethusdt\_20221006.npz Load data/ethusdt\_20221007.npz =========== Summary =========== Sharpe ratio: 21.1 Sortino ratio: 20.3 Risk return ratio: 381.1 Annualised return: 395.88 % Max. draw down: 1.04 % The number of trades per day: 4547 Avg. daily trading volume: 4547 Avg. daily trading amount: 6092144 Max leverage: 2.09 Median leverage: 0.16 ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_41_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_41_1.png) You can see it works even better with other coins as well. In the next example, we will show how to create multiple markets to achieve better risk-adjusted returns. \[23\]: hbt \= HftBacktest( \[\ 'data/ltcusdt\_20230701.npz',\ 'data/ltcusdt\_20230702.npz',\ 'data/ltcusdt\_20230703.npz',\ 'data/ltcusdt\_20230704.npz',\ 'data/ltcusdt\_20230705.npz'\ \], tick\_size\=0.01, lot\_size\=0.001, maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, trade\_list\_size\=10\_000, snapshot\='data/ltcusdt\_20230630\_eod.npz' ) stat \= Stat(hbt) out \= gridtrading\_glft\_mm(hbt, stat.recorder) stat.summary(capital\=2\_500) \=========== Summary =========== Sharpe ratio: 17.8 Sortino ratio: 16.4 Risk return ratio: 207.0 Annualised return: 768.72 % Max. draw down: 3.71 % The number of trades per day: 1912 Avg. daily trading volume: 1912 Avg. daily trading amount: 206843 Max leverage: 1.36 Median leverage: 0.22 ![../_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_43_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_GLFT_Market_Making_Model_and_Grid_Trading_43_1.png) Wrapping up[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#Wrapping-up "Permalink to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Thus far, we have illustrated how to apply the model to a real-world example. For a more effective market-making algorithm, consider dividing this model into the following categories: * Half-spread: As shown, the half-spread is a function of trading intensity and market volatility. An exponential function used for trading intensity might not be suitable for the entire range. You could develop a more refined approach to convert trading intensity to half-spread. Additionally, while historical trading intensity and market volatility are utilized here, you could forecast short-term trading intensity and volatility to respond more agilely to changes in market conditions. This might involve strategies that use news, events, liquidity vacuums, and other factors to predict volatility explosions. * Skew: The skew is also a function of trading intensity and market volatility. In this model, only inventory risk is considered, but you can also account for other risks, particularly when making multiple markets. BARRA is a good example of other risks that can be managed similarly. * Fair Value Pricing: In this model, the fair price is equal to the mid-price, however, you need to incorporate forecasts such as the micro-price and fair value pricing through correlated assets to enhance the strategy. * Hedging: Hedging is especially crucial when making multiple markets, as it serves as a valuable tool for managing risks. We will address a few more topics in upcoming examples. References[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/GLFT%20Market%20Making%20Model%20and%20Grid%20Trading.html#References "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [Dealing with the Inventory Risk - A solution to the market making problem](https://arxiv.org/abs/1105.3115) [Optimal market making](https://arxiv.org/abs/1605.01862) Knight Capital Group [Stochastic Control Theory and High Frequency Trading](https://ieor.columbia.edu/files/seasdepts/industrial-engineering-operations-research/pdf-files/Borden_D_FESeminar_Sp10.pdf) BitMEX Market Making Series [Algo Trading & Market Making](https://blog.bitmex.com/wp-content/uploads/2019/11/Algo-Trading-and-Market-Making.pdf) [How to Market Make Bitcoin Derivatives Lesson 1](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-1/) [How to Market Make Bitcoin Derivatives Lesson 2](https://blog.bitmex.com/how-to-market-make-bitcoin-derivatives-lesson-2/) --- # Making Multiple Markets — hftbacktest * [](https://hftbacktest.readthedocs.io/en/v1.8.4/index.html) * Making Multiple Markets * [Edit on GitHub](https://github.com/nkaz001/hftbacktest/blob/v1.8.4/docs/tutorials/Making%20Multiple%20Markets.ipynb) * * * Making Multiple Markets[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets.html#Making-Multiple-Markets "Permalink to this heading") ======================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets.html#Overview "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------ By diversifying your assets and constructing a market-making book, you can achieve improved risk-adjusted returns through the effects of diversification. In this example, we will demonstrate how the statistics of your market-making portfolio change as you increase the number of assets for which you create markets. To implement Grid Trading using the GLFT market-making model across multiple assets universally without needing to adjust parameters, a few modifications are required: Order quantities vary between assets due to differences in price, trading volume, and liquidity in the order book. To backtest all at once, you need to normalize your order quantities and make adjustments accordingly. In certain assets, market trades primarily take place at the best bid and offer levels. Since we only calculate our trading intensity when market trades match our quotes, you may not achieve adequate trading intensity to suit your trading intensity function in such cases. As a result, you’ll need to explore alternative methods to determine your half spread and skew based on order arrival depths or you need to increase your reaction interval to get more deeper order arrival depth but it leads you to react delayed especially in a fast-moving market. See how \\(adj\_2\\) is determined to normalize different order quantities. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: from numba import njit from numba.typed import Dict from hftbacktest import BUY, SELL from hftbacktest import HftBacktest, Linear, FeedLatency, SquareProbQueueModel, Stat, GTX, GTC import numpy as np @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def gridtrading\_glft\_mm(hbt, stat, order\_qty): arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + .5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) \# To properly calibrate A and K, a sufficient number of data points is required, here, with a minimum of three. \# If market trades only take place at the best bid and offer, an alternative method may be necessary \# to compute half spread and skew, since fitting a function might not be feasible due to insufficient \# data points. \# Alternatively, you can increase the time-step for measuring order arrivals, \# but this could result in a delayed response. half\_spread\_one \= False if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread \= max((c1 + delta / 2 \* c2 \* volatility) \* adj1, 0.5) skew \= c2 \* volatility \* adj2 bid\_depth \= half\_spread + skew \* hbt.position ask\_depth \= half\_spread \- skew \* hbt.position bid\_price \= min(mid\_price\_tick \- bid\_depth, hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(mid\_price\_tick + ask\_depth, hbt.best\_ask\_tick) \* hbt.tick\_size grid\_interval \= max(np.round(half\_spread) \* hbt.tick\_size, hbt.tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. stat.record(hbt) The order quantity is determined to be equivalent to a notional value of $100. \[2\]: from hftbacktest import COL\_SIDE, COL\_PRICE def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(1), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gridtrading\_glft\_mm(hbt, stat.recorder, order\_qty) np.savez( '{}\_stat'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) By utilizing multiprocessing, backtesting of multiple assets can be conducted simultaneously. \[3\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[4\]: import pandas as pd equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() You can see the equity curve of individual assets and notice how combining multiple assets can lead to a smoother equity curve, thereby enhancing risk-adjusted returns. \[5\]: from matplotlib import pyplot as plt for i, asset\_name in enumerate(assets.keys()): plt.figure(i, figsize\=(10, 3)) plt.plot(list(equity\_values.values())\[i\]) plt.grid() plt.title(asset\_name) plt.ylabel('Equity ($)') ![../_images/tutorials_Making_Multiple_Markets_8_0.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_0.png) ![../_images/tutorials_Making_Multiple_Markets_8_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_1.png) ![../_images/tutorials_Making_Multiple_Markets_8_2.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_2.png) ![../_images/tutorials_Making_Multiple_Markets_8_3.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_3.png) ![../_images/tutorials_Making_Multiple_Markets_8_4.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_4.png) ![../_images/tutorials_Making_Multiple_Markets_8_5.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_5.png) ![../_images/tutorials_Making_Multiple_Markets_8_6.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_6.png) ![../_images/tutorials_Making_Multiple_Markets_8_7.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_7.png) ![../_images/tutorials_Making_Multiple_Markets_8_8.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_8.png) ![../_images/tutorials_Making_Multiple_Markets_8_9.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_9.png) ![../_images/tutorials_Making_Multiple_Markets_8_10.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_10.png) ![../_images/tutorials_Making_Multiple_Markets_8_11.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_11.png) ![../_images/tutorials_Making_Multiple_Markets_8_12.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_12.png) ![../_images/tutorials_Making_Multiple_Markets_8_13.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_13.png) ![../_images/tutorials_Making_Multiple_Markets_8_14.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_8_14.png) This presents an equity curve based on the number of assets, which are altcoins excluding BTC and ETH. \[6\]: fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[6\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_10_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_10_1.png) Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets.html#Impact-of-Order-Latency "Permalink to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ When applying amplified feed latency, you can observe a decrease in performance due to the effects of latency. \[7\]: def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=FeedLatency(entry\_latency\_mul\=4, resp\_latency\_mul\=3), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gridtrading\_glft\_mm(hbt, stat.recorder, order\_qty) np.savez( '{}\_stat\_latency1'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[8\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[9\]: equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_latency1.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[9\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_14_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_14_1.png) When actual historical order latency is applied, the performance may deteriorate further compared to when amplified feed latency is used. \[10\]: from hftbacktest import IntpOrderLatency latency\_data \= np.concatenate( \[np.load('../latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gridtrading\_glft\_mm(hbt, stat.recorder, order\_qty) np.savez( '{}\_stat\_latency2'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[11\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[12\]: equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_latency2.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[12\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_18_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_18_1.png) Therefore, understanding your order latency is crucial to achieving more precise backtest results. This understanding underscores the importance of latency reduction for market makers or high-frequency traders. This is why crypto exchanges not only offer maker rebates but also provide low-latency infrastructure to eligible market makers. Simpler model[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets.html#Simpler-model "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------- So far, we only cover \\(\\xi>0\\) case, but \\(\\xi=0\\) case would be more simple and appropriate in practice especially in cryptocurrencies. Revisit the equations (4.6) and (4.7) in [Optimal market making](https://arxiv.org/abs/1605.01862) and explore how they can be applied to real-world scenarios. The optimal bid quote depth, \\(\\delta^{b\*}\_{approx}\\), and ask quote depth, \\(\\delta^{a\*}\_{approx}\\), are derived from the fair price as follows in the case of \\(\\xi=0\\): \\begin{align} \\delta^{b\*}\_{approx}(q) = {1 \\over k} + {{2q + \\Delta} \\over 2}\\sqrt{{{\\gamma \\sigma^2 e} \\over {2A\\Delta k}}} \\label{eq4.6}\\tag{4.6} \\\\ \\delta^{a\*}\_{approx}(q) = {1 \\over k} - {{2q - \\Delta} \\over 2}\\sqrt{{{\\gamma \\sigma^2 e} \\over {2A\\Delta k}}} \\label{eq4.7}\\tag{4.7} \\end{align} Let’s introduce \\(c\_1\\) and \\(c\_2\\) and define them by extracting the volatility 𝜎 from the square root as same as before: \\begin{align} c\_1 = {1 \\over k} \\\\ c\_2 = \\sqrt{{{\\gamma e} \\over {2A\\Delta k}}} \\end{align} Now we can rewrite equations (4.6) and (4.7) as follows: \\begin{align} \\delta^{b\*}\_{approx}(q) = c\_1 + {\\Delta \\over 2} \\sigma c\_2 + q \\sigma c\_2 \\\\ \\delta^{a\*}\_{approx}(q) = c\_1 + {\\Delta \\over 2} \\sigma c\_2 - q \\sigma c\_2 \\end{align} It’s more concise and only need to adjust \\(\\gamma\\) and its effect is more straightforward. \[13\]: @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, stat, gamma, order\_qty): arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000): #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for trade in hbt.last\_trades: side \= trade\[3\] trade\_price\_tick \= trade\[4\] / hbt.tick\_size if side \== BUY: depth \= np.nanmax(\[trade\_price\_tick \- mid\_price\_tick, depth\]) else: depth \= np.nanmax(\[mid\_price\_tick \- trade\_price\_tick, depth\]) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades() prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (hbt.best\_bid\_tick + hbt.best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) \# To properly calibrate A and K, a sufficient number of data points is required, here, with a minimum of three. \# If market trades only take place at the best bid and offer, an alternative method may be necessary \# to compute half spread and skew, since fitting a function might not be feasible due to insufficient \# data points. \# Alternatively, you can increase the time-step for measuring order arrivals, \# but this could result in a delayed response. half\_spread\_one \= False if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= hbt.position / order\_qty bid\_depth \= half\_spread + skew \* normalized\_position ask\_depth \= half\_spread \- skew \* normalized\_position bid\_price \= min(mid\_price\_tick \- bid\_depth, hbt.best\_bid\_tick) \* hbt.tick\_size ask\_price \= max(mid\_price\_tick + ask\_depth, hbt.best\_ask\_tick) \* hbt.tick\_size grid\_interval \= max(np.round(half\_spread) \* hbt.tick\_size, hbt.tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. hbt.clear\_inactive\_orders() \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.int64, np.float64) if hbt.position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price \-= i \* grid\_interval bid\_price\_tick \= round(bid\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[bid\_price\_tick\] \= bid\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== BUY and order.cancellable and order.order\_id not in new\_bid\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_buy\_order(order\_id, order\_price, order\_qty, GTX) \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.int64, np.float64) if hbt.position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price += i \* grid\_interval ask\_price\_tick \= round(ask\_price / hbt.tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[ask\_price\_tick\] \= ask\_price for order in hbt.orders.values(): \# Cancels if an order is not in the new grid. if order.side \== SELL and order.cancellable and order.order\_id not in new\_ask\_orders: hbt.cancel(order.order\_id) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts an order if it doesn't exist. if order\_id not in hbt.orders: hbt.submit\_sell\_order(order\_id, order\_price, order\_qty, GTX) t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. stat.record(hbt) \[14\]: def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gamma \= 0.00005 gridtrading\_glft\_mm(hbt, stat.recorder, gamma, order\_qty) np.savez( '{}\_stat\_sim'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[15\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[16\]: equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_sim.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[16\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_24_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_24_1.png) \[17\]: def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230630\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gamma \= 0.01 gridtrading\_glft\_mm(hbt, stat.recorder, gamma, order\_qty) np.savez( '{}\_stat\_sim2'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[18\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) You can observe a more straight line in the equity curve with higher \\(\\gamma\\), which induces greater skew. However, it also experiences more severe drawdowns in fast-moving markets. Additionally, because of the higher skew, profits are diminished as there’s a greater tendency to close the position. \[19\]: equity\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_sim2.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_values\[asset\_name\] \= equity.resample('5min').last() fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 net\_equity\_rs \= net\_equity\_.resample('1d').last() pnl \= net\_equity\_rs.diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend(legend) plt.grid() plt.ylabel('Cumulative Returns (%)') \[19\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_28_1.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_28_1.png) A Case for More Assets[](https://hftbacktest.readthedocs.io/en/v1.8.4/tutorials/Making%20Multiple%20Markets.html#A-Case-for-More-Assets "Permalink to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- The more assets you make a market for, the better risk-adjusted return you achieve. This effect becomes dramatically evident. \[20\]: with open('assets2.json', 'r') as f: assets \= json.load(f) latency\_data \= np.concatenate( \[np.load('../latency/order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) def backtest(args): asset\_name, asset\_info \= args hbt \= HftBacktest( \['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\], tick\_size\=asset\_info\['tick\_size'\], lot\_size\=asset\_info\['lot\_size'\], maker\_fee\=-0.00005, taker\_fee\=0.0007, order\_latency\=IntpOrderLatency(data\=latency\_data), queue\_model\=SquareProbQueueModel(), asset\_type\=Linear, snapshot\='data/{}\_20230730\_eod.npz'.format(asset\_name), trade\_list\_size\=10000, ) stat \= Stat(hbt) \# Obtains the mid-price of the assset to determine the order quantity. data \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(data\[data\[:, COL\_SIDE\] \== 1\]\[:, COL\_PRICE\]) best\_ask \= min(data\[data\[:, COL\_SIDE\] \== \-1\]\[:, COL\_PRICE\]) mid \= (best\_bid + best\_ask) / 2.0 \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] gamma \= 0.00005 gridtrading\_glft\_mm(hbt, stat.recorder, gamma, order\_qty) np.savez( '{}\_stat\_000005'.format(asset\_name), timestamp\=np.asarray(stat.timestamp), mid\=np.asarray(stat.mid), balance\=np.asarray(stat.balance), position\=np.asarray(stat.position), fee\=np.asarray(stat.fee), ) \[21\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[22\]: equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): stat \= np.load('{}\_stat\_000005.npz'.format(asset\_name)) timestamp \= stat\['timestamp'\] mid \= stat\['mid'\] balance \= stat\['balance'\] position \= stat\['position'\] fee \= stat\['fee'\] equity \= mid \* position + balance \- fee equity \= pd.Series(equity, index\=pd.to\_datetime(timestamp, unit\='us', utc\=True)) equity\_ \= equity.resample('5min').last() pnl \= equity\_.diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity\_ sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) fig \= plt.figure() fig.set\_size\_inches(10, 3) asset\_number \= 0 legend \= \[\] net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.copy() else: net\_equity += equity.copy() if asset\_number % 10 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_ \= (net\_equity / asset\_number) / 2\_000 \# net\_equity\_rs = net\_equity\_.resample('1d').last() \# pnl = net\_equity\_rs.diff() pnl \= net\_equity\_.diff() sr \= pnl.mean() / pnl.std() \* np.sqrt(288) ann\_sr \= sr \* np.sqrt(365) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) (net\_equity\_ \* 100).plot() plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) /tmp/ipykernel\_16711/1802221944.py:16: RuntimeWarning: invalid value encountered in divide sr = np.divide(pnl.mean(), pnl.std()) \[22\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_32_2.png](https://hftbacktest.readthedocs.io/en/v1.8.4/_images/tutorials_Making_Multiple_Markets_32_2.png) --- # Latency Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Latency Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/latency_models.rst.txt) * * * Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#latency-models "Link to this heading") ============================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------- Latency is an important factor that you need to take into account when you backtest your HFT strategy. HftBacktest has three types of latencies. ![_images/latencies.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/latencies.png) * Feed latency This is the latency between the time the exchange sends the feed events such as order book change or trade and the time it is received by the local. This latency is dealt with through two different timestamps: local timestamp and exchange timestamp. * Order entry latency This is the latency between the time you send an order request and the time it is processed by the exchange’s matching engine. * Order response latency This is the latency between the time the exchange’s matching engine processes an order request and the time the order response is received by the local. The response to your order fill is also affected by this type of latency. ![_images/latency-comparison.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/latency-comparison.png) Order Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#order-latency-models "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- HftBacktest provides the following order latency models and you can also implement your own latency model. ### ConstantLatency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#constantlatency "Link to this heading") It’s the most basic model that uses constant latencies. You just set the latencies. You can find details below. * [ConstantLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ConstantLatency.html) and [`constant_latency`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency "hftbacktest.BacktestAsset.constant_latency") ### IntpOrderLatency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#intporderlatency "Link to this heading") This model interpolates order latency based on the actual order latency data. This is the most accurate among the provided models if you have the data with a fine time interval. You can collect the latency data by submitting unexecutable orders regularly. You can find details below. * [IntpOrderLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html) and [`intp_order_latency`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency "hftbacktest.BacktestAsset.intp_order_latency") **Data example** req\_ts (request timestamp at local), exch\_ts (exchange timestamp), resp\_ts (receipt timestamp at local), \_padding 1670026844751525000, 1670026844759000000, 1670026844762122000, 0 1670026845754020000, 1670026845762000000, 1670026845770003000, 0 ### FeedLatency[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#feedlatency "Link to this heading") If the live order latency data is unavailable, you can generate artificial order latency using feed latency. Please refer to [this tutorial](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Order%20Latency%20Data.html) for guidance. ### Implement your own order latency model[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/latency_models.html#implement-your-own-order-latency-model "Link to this heading") You need to implement the following trait. * [LatencyModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.LatencyModel.html) Please refer to [the latency model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/latency.rs) . --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * JIT Compilation Overhead * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/jit_compilation_overhead.rst.txt) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/jit_compilation_overhead.html#jit-compilation-overhead "Link to this heading") =========================================================================================================================================================== HftBacktest takes advantage of Numba’s capabilities, relying on Numba JIT’ed classes. As a result, importing HftBacktest requires JIT compilation, which may take a few seconds. Additionally, the strategy function needs to be JIT’ed’ for performant backtesting, which also takes time to compile. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature. See the example below. from numba import njit \# May take a few seconds from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest \# Enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. asset \= ( BacktestAsset() .linear\_asset(1.0) .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .no\_partial\_fill\_exchange() .intp\_order\_latency(\[\ 'data/latency\_20221003.npz',\ 'data/latency\_20221004.npz',\ 'data/latency\_20221005.npz',\ 'data/latency\_20221006.npz',\ 'data/latency\_20221007.npz'\ \]) .power\_prob\_queue\_model3(3.0) .tick\_size(0.01) .lot\_size(0.001) .trading\_value\_fee\_model(0.0002, 0.0007) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) algo(arguments, hbt) --- # Initialization — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Initialization * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/reference/initialization.rst.txt) * * * Initialization[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#initialization "Link to this heading") ======================================================================================================================================= _class_ BacktestAsset[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest.html#BacktestAsset) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset "Link to this definition") data(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest.html#BacktestAsset.data) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/initialization.html#hftbacktest.BacktestAsset.data "Link to this definition") Sets the feed data. Parameters: **data** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") _|_ [_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _\]_ _|_ [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ _dtype__(__\[__(__'ev'__,_ _' ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Probability Queue Position Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Probability Queue Position Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Probability%20Queue%20Models.ipynb.txt) * * * Probability Queue Position Models[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Probability%20Queue%20Models.html#Probability-Queue-Position-Models "Link to this heading") =========================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Probability%20Queue%20Models.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- Here, we will demonstrate how queue position models affect order fill simulation and, ultimately, the strategy’s performance. It is essential for accurate backtesting to find the proper queue position modeling by comparing backtest and real trading results. In this context, we will illustrate comparisons by changing queue position models. By doing this, you can determine the appropriate queue position model that aligns with the backtesting and real trading results. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[2\]: def backtest(args): asset\_name, asset\_info, model \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) if model \== 'SquareProbQueueModel': asset.power\_prob\_queue\_model(2) elif model \== 'LogProbQueueModel2': asset.log\_prob\_queue\_model2() elif model \== 'PowerProbQueueModel3': asset.power\_prob\_queue\_model3(3) else: raise ValueError hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name)) \[3\]: %%capture from multiprocessing import Pool import json with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'SquareProbQueueModel') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'LogProbQueueModel2') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'PowerProbQueueModel3') for k, v in assets.items()\])) \[4\]: import polars as pl from matplotlib import pyplot as plt def compute\_net\_equity(model): equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number \== 100: \# 5\_000 is capital for each trading asset. return net\_equity.with\_columns( (pl.col('equity') / asset\_number / 5\_000).alias('equity') ) np.seterr(divide\='ignore', invalid\='ignore') fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] for model in \['SquareProbQueueModel', 'LogProbQueueModel2', 'PowerProbQueueModel3'\]: net\_equity\_ \= compute\_net\_equity(model) pnl \= net\_equity\_\['equity'\].diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('100 assets, Daily SR={:.2f}, {}'.format(sr, model)) plt.plot(net\_equity\_\['timestamp'\], net\_equity\_\['equity'\] \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[4\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Probability_Queue_Models_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Probability_Queue_Models_4_1.png) --- # Order Fill — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Order Fill * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/order_fill.rst.txt) * * * Order Fill[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#order-fill "Link to this heading") ================================================================================================================= Exchange Models[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#exchange-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------------- HftBacktest is a market-data replay-based backtesting tool, which means your order cannot make any changes to the simulated market, no market impact is considered. Therefore, one of the most important assumptions is that your order is small enough not to make any market impact. In the end, you must test it in a live market with real market participants and adjust your backtesting based on the discrepancies between the backtesting results and the live outcomes. Hftbacktest offers two types of exchange simulation. [NoPartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#order-fill-no-partial-fill-exchange) is the default exchange simulation where no partial fills occur. [PartialFillExchange](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#order-fill-partial-fill-exchange) is the extended exchange simulation that accounts for partial fills in specific cases. Since the market-data replay-based backtesting cannot alter the market, some partial fill cases may still be unrealistic, such as taking market liquidity. This is because even if your order takes market liquidity, the replayed market data’s market depth and trades cannot change. It is essential to understand the underlying assumptions in each backtesting simulation. ### NoPartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#nopartialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#conditions-for-full-execution "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price * Your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price * Your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#liquidity-taking-order "Link to this heading") > Regardless of the quantity at the best, liquidity-taking orders will be fully executed at the best. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [NoPartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.NoPartialFillExchange.html) and [`no_partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange "hftbacktest.BacktestAsset.no_partial_fill_exchange") ### PartialFillExchange[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#partialfillexchange "Link to this heading") #### Conditions for Full Execution[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#id2 "Link to this heading") Buy order in the order book * Your order price >= the best ask price * Your order price > sell trade price Sell order in the order book * Your order price <= the best bid price * Your order price < buy trade price #### Conditions for Partial Execution[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#conditions-for-partial-execution "Link to this heading") Buy order in the order book * Filled by (remaining) sell trade quantity: your order is at the front of the queue && your order price == sell trade price Sell order in the order book * Filled by (remaining) buy trade quantity: your order is at the front of the queue && your order price == buy trade price #### Liquidity-Taking Order[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#id3 "Link to this heading") > Liquidity-taking orders will be executed based on the quantity of the order book, even though the best price and quantity do not change due to your execution. Be aware that this may cause unrealistic fill simulations if you attempt to execute a large quantity. You can find details below. * [PartialFillExchange](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/proc/struct.PartialFillExchange.html) and [`partial_fill_exchange`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange "hftbacktest.BacktestAsset.partial_fill_exchange") Queue Models[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#queue-models "Link to this heading") --------------------------------------------------------------------------------------------------------------------- Knowing your order’s queue position is important to achieve accurate order fill simulation in backtesting depending on the liquidity of an order book and trading activities. If an exchange doesn’t provide Market-By-Order, you have to guess it by modeling. HftBacktest currently only supports Market-By-Price that is most crypto exchanges provide and it provides the following queue position models for order fill simulation. Please refer to the details at Models . ![_images/liquidity-and-trade-activities.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/liquidity-and-trade-activities.png) ### RiskAverseQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#riskaversequeuemodel "Link to this heading") This model is the most conservative model in terms of the chance of fill in the queue. The decrease in quantity by cancellation or modification in the order book happens only at the tail of the queue so your order queue position doesn’t change. The order queue position will be advanced only if a trade happens at the price. You can find details below. * [RiskAdverseQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.RiskAdverseQueueModel.html) and [`risk_adverse_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model "hftbacktest.BacktestAsset.risk_adverse_queue_model") ### ProbQueueModel[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#probqueuemodel "Link to this heading") Based on a probability model according to your current queue position, the decrease in quantity happens at both before and after the queue position. So your queue position is also advanced according to the probability. This model is implemented as described in * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) * [https://rigtorp.se/2013/06/08/estimating-order-queue-position.html](https://rigtorp.se/2013/06/08/estimating-order-queue-position.html) You can find details below. * [ProbQueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ProbQueueModel.html) * [PowerProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc.html) and [`power_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model "hftbacktest.BacktestAsset.power_prob_queue_model") * [PowerProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc2.html) and [`power_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2 "hftbacktest.BacktestAsset.power_prob_queue_model2") * [PowerProbQueueFunc3](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.PowerProbQueueFunc3.html) and [`power_prob_queue_model3`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3 "hftbacktest.BacktestAsset.power_prob_queue_model3") * [LogProbQueueFunc](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc.html) and [`log_prob_queue_model`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model "hftbacktest.BacktestAsset.log_prob_queue_model") * [LogProbQueueFunc2](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.LogProbQueueFunc2.html) and [`log_prob_queue_model2`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2 "hftbacktest.BacktestAsset.log_prob_queue_model2") By default, three variations are provided. These three models have different probability profiles. ![_images/probqueuemodel.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/probqueuemodel.png) The function f = log(1 + x) exhibits a different probability profile depending on the total quantity at the price level, unlike power functions. ![_images/probqueuemodel_log.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/probqueuemodel_log.png) ![_images/probqueuemodel2.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/probqueuemodel2.png) ![_images/probqueuemodel3.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/probqueuemodel3.png) When you set the function f, it should be as follows. * The probability at 0 should be 0 because if the order is at the head of the queue, all decreases should happen after the order. * The probability at 1 should be 1 because if the order is at the tail of the queue, all decreases should happen before the order. You can see the comparison of the models [here](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Probability%20Queue%20Models.html) . ### Implement a custom queue model[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#implement-a-custom-queue-model "Link to this heading") You need to implement the following traits in Rust based on your usage requirements. * [QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.QueueModel.html) * [L3QueueModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.L3QueueModel.html) Please refer to [the queue model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/queue.rs) . References[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#references "Link to this heading") ----------------------------------------------------------------------------------------------------------------- This is initially implemented as described in the following articles. * [http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf](http://www.math.ualberta.ca/~cfrei/PIMS/Almgren5.pdf) * [https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book](https://quant.stackexchange.com/questions/3782/how-do-we-estimate-position-of-our-order-in-order-book) --- # Examples — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Examples * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/tutorials/examples.rst.txt) * * * Examples[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/tutorials/examples.html#examples "Link to this heading") ===================================================================================================================== You can find more examples [here](https://github.com/nkaz001/hftbacktest/tree/master/examples) --- # Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/data.rst.txt) * * * Data[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/data.html#data "Link to this heading") =============================================================================================== Please see [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) or [Data Preparation](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Data%20Preparation.html) regarding collecting and converting the feed data. Format[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/data.html#format "Link to this heading") --------------------------------------------------------------------------------------------------- hftbacktest can digest a numpy structured array. The data has 8 fields in the following order. You can also find details in [Event](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/struct.Event.html) . * ev (u64): You can find the possible flag combinations in [Constants](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/index.html#constants) . * exch\_ts (i64): Exchange timestamp, which is the time at which the event occurs on the exchange. * local\_ts (i64): Local timestamp, which is the time at which the event is received by the local. * px (f64): Price * qty (f64): Quantity * order\_id (u64): Order ID is only for the L3 Market-By-Order feed. * ival (i64): Reserved for an additional i64 value * faval (f64): Reserved for an additional f64 value **Raw data** > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22171.70 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22187.30 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.30 | 0.270 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.70 | 0.423 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22195.20 | 2.075 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22209.60 | 4.506 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205116000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | Validation[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/data.html#validation "Link to this heading") ----------------------------------------------------------------------------------------------------------- 1. All timestamps must be in the correct order, chronological order. There can be cases where an event happens before another at the exchange, resulting in an earlier exchange timestamp, but it is received locally after the other event. This reverses the chronological order of exchange and local timestamps. To handle this situation, hftbacktest uses the [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. Events flagged with [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") should be in chronological order according to the exchange timestamp, while events flagged with [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") should be in chronological order according to the local timestamp. 2. The exchange timestamp must be earlier than the local timestamp; the feed latency must be positive. Due to potential errors in time synchronization between two sites, the local timestamp may be earlier than the exchange timestamp, resulting in negative latency. The best way to address this is to improve time synchronization using PTP (Precision Time Protocol), which minimizes the possibility of negative latency. However, by adding a base latency or offsetting the size of the negative latency, you can ensure that the data remains valid with only positive latencies, where the local timestamp is always later than the exchange timestamp of the event. See the following example. The exchange timestamp of the depth feed is advanced to the prior trade feed even though the depth feed is received after the trade feed. > 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} This should be converted into the following form. HftBacktest provides [`correct_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#hftbacktest.data.correct_event_order "hftbacktest.data.correct_event_order") method to automatically correct this issue. [`validate_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/data_validation.html#hftbacktest.data.validate_event_order "hftbacktest.data.validate_event_order") helps to check if this issue exists. > EXCH\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > LOCAL\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212385000 | 22177.90 | 0.300 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212480000 | 22177.90 | 0.119 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212621000 | 22177.90 | 0.005 | 0 | 0 | 0.0 | --- # Backtester — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Backtester * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/reference/backtester.rst.txt) * * * Backtester[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#backtester "Link to this heading") =========================================================================================================================== _class_ HashMapMarketDepthBacktest[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/binding.html#HashMapMarketDepthBacktest) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest "Link to this definition") Parameters: **ptr** (_void\*_) _property_ current\_timestamp_: int64_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp "Link to this definition") In backtesting, this timestamp reflects the time at which the backtesting is conducted within the provided data. depth(_asset\_no_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/binding.html#HashMapMarketDepthBacktest.depth) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth "Link to this definition") Parameters: **asset\_no** (_uint64_) – Asset number from which the market depth will be retrieved. Returns: The depth of market of the specific asset. Return type: [_HashMapMarketDepth_](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth "hftbacktest.binding.HashMapMarketDepth") _property_ num\_assets_: uint64_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets "Link to this definition") Returns the number of assets. position(_asset\_no_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/binding.html#HashMapMarketDepthBacktest.position) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position "Link to this definition") Parameters: **asset\_no** (_uint64_) – Asset number from which the position will be retrieved. Returns: The quantity of the held position. Return type: float64 state\_values(_asset\_no_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/binding.html#HashMapMarketDepthBacktest.state_values) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values "Link to this definition") Parameters: **asset\_no** (_uint64_) – Asset number from which the state values will be retrieved. Returns: The state’s values. Return type: _StateValues_ last\_trades(_asset\_no_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_modules/hftbacktest/binding.html#HashMapMarketDepthBacktest.last_trades) [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades "Link to this definition") Parameters: **asset\_no** (_uint64_) – Asset number from which the trades will be retrieved. Returns: An array of Event representing trades occurring in the market for the specific asset. Return type: [_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.1)") \[[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ , dtype(\[(‘ev’, ‘ * [`NONE`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NONE "hftbacktest.order.NONE") > for no ongoing request. > > * [`NEW`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NEW "hftbacktest.order.NEW") > for submitting a new order. > > * [`CANCELED`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.CANCELED "hftbacktest.order.CANCELED") > for canceling the order. > _property_ status_: uint8_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.status "Link to this definition") Returns the order status. This can be one of the following values, but may vary depending on the exchange model. * [`NONE`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NONE "hftbacktest.order.NONE") * [`NEW`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.NEW "hftbacktest.order.NEW") * [`EXPIRED`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.EXPIRED "hftbacktest.order.EXPIRED") * [`FILLED`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FILLED "hftbacktest.order.FILLED") * [`CANCELED`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.CANCELED "hftbacktest.order.CANCELED") * [`PARTIALLY_FILLED`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "hftbacktest.order.PARTIALLY_FILLED") _property_ side_: uint8_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.side "Link to this definition") Returns the order side. * [`BUY`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.BUY "hftbacktest.order.BUY") * [`SELL`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.SELL "hftbacktest.order.SELL") _property_ time\_in\_force_: uint8_[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/backtester.html#hftbacktest.order.Order.time_in_force "Link to this definition") Returns the Time-In-Force of the order. This can be one of the following values, but may vary depending on the exchange model. > * [`GTC`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTC "hftbacktest.order.GTC") > > * [`GTX`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.GTX "hftbacktest.order.GTX") > > * [`FOK`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.FOK "hftbacktest.order.FOK") > > * [`IOC`](https://hftbacktest.readthedocs.io/en/py-v2.1.0/reference/constants.html#hftbacktest.order.IOC "hftbacktest.order.IOC") > --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Debugging Backtesting and Live Discrepancies * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/debugging_backtesting_and_live_discrepancies.rst.txt) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Link to this heading") ======================================================================================================================================================================================================================= Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as `PowerProbQueueModel` series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/py-v2.3.0/order_fill.html#order-fill-prob-queue-model) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # Market Making with Alpha - Order Book Imbalance — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Market Making with Alpha - Order Book Imbalance * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.ipynb.txt) * * * Market Making with Alpha - Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Market-Making-with-Alpha---Order-Book-Imbalance "Link to this heading") ======================================================================================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Overview "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Order Book Imbalance, also known as Order Flow Imbalance, is a widely recognized microstructure indicator often analyzed alongside trade flow. This concept has several derivatives, including the Micro-Price, VAMP (Volume Adjusted Mid Price), Weighted-Depth Order Book Price, and Static Order Book Imbalance, among others. These derivatives may also undergo statistical adjustments, such as standardization. Extensive information is available online regarding these indicators. In the examples that follow, we will these indicators. ### Reference[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Reference "Link to this heading") * [The Micro-Price: A High Frequency Estimator of Future Prices](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2970694) * [Mind the Gaps: Short-Term Crypto Price Prediction](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4351947) * [Market microstructure signals](https://blog.headlandstech.com/2017/08/) **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit def obi\_mm( hbt, stat, half\_spread, skew, c1, looking\_depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ): asset\_no \= 0 imbalance\_timeseries \= np.full(30\_000\_000, np.nan, np.float64) tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size t \= 0 roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 sum\_ask\_qty \= 0.0 from\_tick \= max(depth.best\_ask\_tick, roi\_lb\_tick) upto\_tick \= min(int(np.floor(mid\_price \* (1 + looking\_depth) / tick\_size)), roi\_ub\_tick) for price\_tick in range(from\_tick, upto\_tick): sum\_ask\_qty += depth.ask\_depth\[price\_tick \- roi\_lb\_tick\] sum\_bid\_qty \= 0.0 from\_tick \= min(depth.best\_bid\_tick, roi\_ub\_tick) upto\_tick \= max(int(np.ceil(mid\_price \* (1 \- looking\_depth) / tick\_size)), roi\_lb\_tick) for price\_tick in range(from\_tick, upto\_tick, \-1): sum\_bid\_qty += depth.bid\_depth\[price\_tick \- roi\_lb\_tick\] imbalance\_timeseries\[t\] \= sum\_bid\_qty \- sum\_ask\_qty \# Standardizes the order book imbalance timeseries for a given window m \= np.nanmean(imbalance\_timeseries\[max(0, t + 1 \- window):t + 1\]) s \= np.nanstd(imbalance\_timeseries\[max(0, t + 1 \- window):t + 1\]) alpha \= np.divide(imbalance\_timeseries\[t\] \- m, s) #-------------------------------------------------------- \# Computes bid price and ask price. order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) fair\_price \= mid\_price + c1 \* alpha normalized\_position \= position / order\_qty reservation\_price \= fair\_price \- skew \* normalized\_position bid\_price \= min(np.round(reservation\_price \- half\_spread), best\_bid) ask\_price \= max(np.round(reservation\_price + half\_spread), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) t += 1 if t \>= len(imbalance\_timeseries): raise Exception \# Records the current state for stat calculation. stat.record(hbt) \[2\]: %%time roi\_lb \= 10000 roi\_ub \= 50000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/btcusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/btcusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 80 skew \= 3.5 c1 \= 160 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_btcusdt.npz') CPU times: user 32min 23s, sys: 43.9 s, total: 33min 7s Wall time: 33min 5s \[3\]: data \= np.load('stats/obi\_btcusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[3\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-30 23:55:00 | 10.829336 | 13.5994 | 0.342371 | 0.037249 | 4119.876838 | 82.397448 | 9.191522 | 0.000139 | 2.6383e6 | \[4\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_4_0.png) \[5\]: %%time roi\_lb \= 0 roi\_ub \= 3000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/ethusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/ethusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.01) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 5 skew \= 0.2 c1 \= 10 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_ethusdt.npz') CPU times: user 27min 37s, sys: 38.3 s, total: 28min 15s Wall time: 28min 16s \[6\]: data \= np.load('stats/obi\_ethusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[6\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-31 23:55:00 | 9.017874 | 11.140311 | 0.299582 | 0.055187 | 4112.621933 | 82.252375 | 5.428451 | 0.000118 | 2.6036e6 | \[7\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_7_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_7_0.png) Another approach is to generate trading volume to qualify as a market maker and receive rebates. This strategy involves maintaining a high skew and tight spread. While the strategy itself may not be profitable or may incur losses, it can help achieve market maker status. \[9\]: %%time roi\_lb \= 10000 roi\_ub \= 50000 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230501, 20230532)\] ) asset \= ( BacktestAsset() .data(\['data2/btcusdt\_{}.npz'.format(date) for date in range(20230501, 20230532)\]) .initial\_snapshot('data2/btcusdt\_20230430\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 10 skew \= 2 c1 \= 20 depth \= 0.001 \# 0.1% from the mid price interval \= 500\_000\_000 \# 500ms window \= 600\_000\_000\_000 / interval \# 10min order\_qty\_dollar \= 25\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) recorder.to\_npz('stats/obi\_vg\_btcusdt.npz') CPU times: user 30min 13s, sys: 44.1 s, total: 30min 57s Wall time: 31min 2s \[10\]: data \= np.load('stats/obi\_vg\_btcusdt.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[10\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2023-05-01 00:00:00 | 2023-05-30 23:55:00 | 14.0088 | 17.366641 | 0.129901 | 0.00928 | 8368.135201 | 209.203461 | 13.998514 | 0.000021 | 536859.7998 | \[11\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_11_0.png) Update on Backtesting Results for 2025[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.html#Update-on-Backtesting-Results-for-2025 "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Updated backtesting results for February 2025 using the same parameters as in May 2023, except for the `power_prob_queue_model` parameter, which was changed from 2 to 3 to reflect market changes and a more challenging fill in the queue. \[13\]: import datetime start\_date \= datetime.datetime.strptime('20250101', '%Y%m%d') end\_date \= datetime.datetime.strptime('20250228', '%Y%m%d') \[14\]: %%time roi\_lb \= 50000 roi\_ub \= 150000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime("%Y%m%d"))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20241231\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 30\_000\_000) half\_spread \= 80 skew \= 3.5 c1 \= 160 depth \= 0.025 \# 2.5% from the mid price interval \= 1\_000\_000\_000 \# 1s window \= 3\_600\_000\_000\_000 / interval \# 1hour order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 50 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size obi\_mm( hbt, recorder.recorder, half\_spread, skew, c1, depth, interval, window, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/obi\_btcusdt\_2025.npz') CPU times: user 1h 56min 52s, sys: 8min 33s, total: 2h 5min 25s Wall time: 1h 29min 36s You can see from the following report that the order book imbalance continues to work consistently. However, the return per trade drops from 0.0139% to 0.0086%, including the 0.005% rebates. This again highlights the importance of rebates and the corresponding fee structure for market makers. \[16\]: data \= np.load('stats/obi\_btcusdt\_2025.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=2\_500\_000) ) stats.summary() \[16\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2025-01-01 00:00:00 | 2025-02-28 23:55:00 | 5.369875 | 7.198137 | 0.459649 | 0.097893 | 4533.741393 | 90.675217 | 4.695446 | 0.000086 | 2.6075e6 | \[17\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_17_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Order_Book_Imbalance_17_0.png) --- # Level-3 Backtesting — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Level-3 Backtesting * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Level-3%20Backtesting.ipynb.txt) * * * Level-3 Backtesting[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Level-3%20Backtesting.html#Level-3-Backtesting "Link to this heading") ======================================================================================================================================================== The Level-3 feed data for HftBacktest is built from DataBento’s CME Market-By-Order data \[1\]: from hftbacktest.data.utils import databento for date in range(20240609, 20240615): databento.convert(f'data/db/glbx-mdp3-{date}.mbo.dbn.zst', 'BTCM4', output\_filename\=f'data/BTCM4\_{date}\_l3.npz') Correcting the latency Correcting the event order Saving to data/BTCM4\_20240609\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240610\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240611\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240612\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240613\_l3.npz Correcting the latency Correcting the event order Saving to data/BTCM4\_20240614\_l3.npz \[2\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTC, LIMIT @njit def gridtrading(hbt, recorder, skew): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size grid\_num \= 10 max\_position \= 5 grid\_interval \= tick\_size \* 1 half\_spread \= tick\_size \* 0.4 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 order\_qty \= 1 \# np.round(notional\_order\_qty / mid\_price / hbt.depth(asset\_no).lot\_size) \* hbt.depth(asset\_no).lot\_size \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf alpha \= 0 reservation\_price \= mid\_price + alpha \- skew \* tick\_size \* position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): \# position \* mid\_price < max\_notional\_position for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): \# position \* mid\_price > -max\_notional\_position for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTC, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTC, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[3\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/BTCM4\_20240609\_l3.npz',\ 'data/BTCM4\_20240610\_l3.npz',\ 'data/BTCM4\_20240611\_l3.npz',\ 'data/BTCM4\_20240612\_l3.npz',\ 'data/BTCM4\_20240613\_l3.npz',\ 'data/BTCM4\_20240614\_l3.npz',\ \]) .linear\_asset(5) .constant\_latency(100\_000, 100\_000) .l3\_fifo\_queue\_model() .no\_partial\_fill\_exchange() .trading\_qty\_fee\_model(5, 5) .tick\_size(5) .lot\_size(1) .roi\_lb(0.0) .roi\_ub(100000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_0000) \[4\]: %%time gridtrading(hbt, recorder.recorder, 0.5) \_ \= hbt.close() CPU times: user 35.6 s, sys: 336 ms, total: 35.9 s Wall time: 32.4 s \[5\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).contract\_size(5).stats(book\_size\=1\_000\_000) l3\_backtest\_equity \= stats.entire\['equity\_wo\_fee'\] stats.plot() ![../_images/tutorials_Level-3_Backtesting_5_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Level-3_Backtesting_5_0.png) The following code constructs Level-2 data from Level-3 data for the purpose of comparing backtesting results between Level-3 and Level-2. Level-2 estimates queue positions using a model, whereas Level-3 determines queue positions directly from the order data. \[6\]: from hftbacktest.data import correct\_event\_order, validate\_event\_order from hftbacktest import ( EXCH\_EVENT, LOCAL\_EVENT, TRADE\_EVENT, DEPTH\_EVENT, DEPTH\_CLEAR\_EVENT, ADD\_ORDER\_EVENT, MODIFY\_ORDER\_EVENT, CANCEL\_ORDER\_EVENT, FILL\_EVENT, BUY\_EVENT, SELL\_EVENT, event\_dtype ) from numba.experimental import jitclass from numba.types import DictType, int64 @jitclass class L3MarketDepth: bid\_depth: DictType(int64, float64) ask\_depth: DictType(int64, float64) order\_book\_px: DictType(uint64, float64) order\_book\_qty: DictType(uint64, float64) tick\_size: float64 def \_\_init\_\_(self, tick\_size): self.bid\_depth \= Dict.empty(int64, float64) self.ask\_depth \= Dict.empty(int64, float64) self.order\_book\_px \= Dict.empty(uint64, float64) self.order\_book\_qty \= Dict.empty(uint64, float64) self.tick\_size \= tick\_size def add\_order(self, ev): if ev.order\_id in self.order\_book\_qty: print('add\_order: OrderIdExist', ev.order\_id) raise ValueError self.order\_book\_px\[ev.order\_id\] \= ev.px; l2\_ev \= np.empty(1, event\_dtype) l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT price\_tick \= int(round(ev.px / self.tick\_size)) if ev.ev & BUY\_EVENT \== BUY\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= ev.qty; if price\_tick not in self.bid\_depth: self.bid\_depth\[price\_tick\] \= 0.0 self.bid\_depth\[price\_tick\] += ev.qty l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= \-ev.qty; if price\_tick not in self.ask\_depth: self.ask\_depth\[price\_tick\] \= 0.0 self.ask\_depth\[price\_tick\] += ev.qty l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[0\] def modify\_order(self, ev): if ev.order\_id not in self.order\_book\_qty: print('modify\_order: OrderNotFound', ev.order\_id) raise ValueError prev\_px \= self.order\_book\_px\[ev.order\_id\] prev\_qty \= self.order\_book\_qty\[ev.order\_id\] l2\_ev \= np.empty(2, event\_dtype) l2\_ev\[1\] \= l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT n \= 0 if prev\_qty \> 0: price\_tick \= int(round(prev\_px / self.tick\_size)) self.bid\_depth\[price\_tick\] \-= prev\_qty if int(round(prev\_px / self.tick\_size)) != int(round(ev.px / self.tick\_size)): l2\_ev\[0\].px \= prev\_px l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) n \= 1 elif prev\_qty < 0: price\_tick \= int(round(prev\_px / self.tick\_size)) self.ask\_depth\[price\_tick\] \-= np.abs(prev\_qty) if int(round(prev\_px / self.tick\_size)) != int(round(ev.px / self.tick\_size)): l2\_ev\[0\].px \= prev\_px l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) n \= 1 self.order\_book\_px\[ev.order\_id\] \= ev.px; price\_tick \= int(round(ev.px / self.tick\_size)) if ev.ev & BUY\_EVENT \== BUY\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= ev.qty; if price\_tick not in self.bid\_depth: self.bid\_depth\[price\_tick\] \= 0.0 self.bid\_depth\[price\_tick\] += ev.qty l2\_ev\[n\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: self.order\_book\_qty\[ev.order\_id\] \= \-ev.qty; if price\_tick not in self.ask\_depth: self.ask\_depth\[price\_tick\] \= 0.0 self.ask\_depth\[price\_tick\] += ev.qty l2\_ev\[n\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[:n + 1\] def cancel\_order(self, ev): if ev.order\_id not in self.order\_book\_qty: print('cancel\_order: OrderNotFound', ev.order\_id, ev) raise ValueError del self.order\_book\_px\[ev.order\_id\] del self.order\_book\_qty\[ev.order\_id\] l2\_ev \= np.empty(1, event\_dtype) l2\_ev\[0\] \= ev l2\_ev\[0\].ev \= (l2\_ev\[0\].ev & ~0xff) | DEPTH\_EVENT if ev.ev & BUY\_EVENT \== BUY\_EVENT: price\_tick \= int(round(ev.px / self.tick\_size)) self.bid\_depth\[price\_tick\] \-= ev.qty l2\_ev\[0\].qty \= round(self.bid\_depth\[price\_tick\]) elif ev.ev & SELL\_EVENT \== SELL\_EVENT: price\_tick \= int(round(ev.px / self.tick\_size)) self.ask\_depth\[price\_tick\] \-= ev.qty l2\_ev\[0\].qty \= round(self.ask\_depth\[price\_tick\]) return l2\_ev\[0\] def clear(self): self.order\_book\_px.clear() self.order\_book\_qty.clear() self.bid\_depth.clear() self.ask\_depth.clear() @njit def convert\_l3\_to\_l2(data, tick\_size): result \= np.empty(len(data) \* 4, event\_dtype) local\_md \= L3MarketDepth(tick\_size) exch\_md \= L3MarketDepth(tick\_size) rn \= 0 for i in range(len(data)): if data\[i\].ev & (EXCH\_EVENT | LOCAL\_EVENT) \== EXCH\_EVENT | LOCAL\_EVENT: if data\[i\].ev & 0xff \== ADD\_ORDER\_EVENT: result\[rn\] \= exch\_md.add\_order(data\[i\]) rn += 1 elif data\[i\].ev & 0xff \== MODIFY\_ORDER\_EVENT: l2\_ev \= exch\_md.modify\_order(data\[i\]) result\[rn\] \= l2\_ev\[0\] rn += 1 if len(l2\_ev) \== 2: result\[rn\] \= l2\_ev\[1\] rn += 1 elif data\[i\].ev & 0xff \== CANCEL\_ORDER\_EVENT: result\[rn\] \= exch\_md.cancel\_order(data\[i\]) rn += 1 elif data\[i\].ev & 0xff \== FILL\_EVENT: continue elif data\[i\].ev & 0xff \== DEPTH\_CLEAR\_EVENT: exch\_md.clear() result\[rn\] \= data\[i\] rn += 1 else: result\[rn\] \= data\[i\] rn += 1 else: \# DataBento's CME data is aligned in both local and exchange timestamps. raise ValueError return result\[:rn\] \[7\]: for date in range(20240609, 20240615): l3 \= databento.convert(f'data/db/glbx-mdp3-{date}.mbo.dbn.zst', 'BTCM4') tick\_size \= 5 l2 \= convert\_l3\_to\_l2(l3, tick\_size) data \= correct\_event\_order( l2, np.argsort(l2\['exch\_ts'\], kind\='mergesort'), np.argsort(l2\['local\_ts'\], kind\='mergesort') ) validate\_event\_order(data) np.savez\_compressed(f'data/BTCM4\_{date}\_l2.npz', data\=data) Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order Correcting the latency Correcting the event order \[8\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ 'data/BTCM4\_20240609\_l2.npz',\ 'data/BTCM4\_20240610\_l2.npz',\ 'data/BTCM4\_20240611\_l2.npz',\ 'data/BTCM4\_20240612\_l2.npz',\ 'data/BTCM4\_20240613\_l2.npz',\ 'data/BTCM4\_20240614\_l2.npz',\ \]) .linear\_asset(5) .constant\_latency(100\_000, 100\_000) .power\_prob\_queue\_model3(3.0) .no\_partial\_fill\_exchange() .trading\_qty\_fee\_model(5, 5) .tick\_size(5) .lot\_size(1) .roi\_lb(0.0) .roi\_ub(100000.0) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 5\_000\_0000) \[9\]: %%time gridtrading(hbt, recorder.recorder, 0.5) \_ \= hbt.close() CPU times: user 28.9 s, sys: 401 ms, total: 29.3 s Wall time: 24.9 s \[10\]: stats \= LinearAssetRecord(recorder.get(0)).contract\_size(5).stats(book\_size\=1\_000\_000) l2\_backtest\_equity \= stats.entire\['equity\_wo\_fee'\] stats.plot() ![../_images/tutorials_Level-3_Backtesting_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Level-3_Backtesting_11_0.png) \[11\]: from matplotlib import pyplot as plt plt.plot(l3\_backtest\_equity) plt.plot(l2\_backtest\_equity) plt.legend(\['Level-3', 'Level-2'\]) \[11\]: ![../_images/tutorials_Level-3_Backtesting_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Level-3_Backtesting_12_1.png) The impact of the difference can vary depending on the characteristics of the strategy; for some strategies, Level-2 estimation may be sufficiently accurate, while for others, it may not be. This comparison is intended to highlight these differences. In markets that only provide Level-2 data, it is important to develop a realistic queue position model based on live trading data. Although Level-3 data offers direct order queue position information, it is still crucial to validate backtesting results against live trading results. For example, in this CME Level-3 backtest, the market depth doesn’t include implied orders. --- # Market Making with Alpha - Basis — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Market Making with Alpha - Basis * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.ipynb.txt) * * * Market Making with Alpha - Basis[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Market%20Making%20with%20Alpha%20-%20Basis.html#Market-Making-with-Alpha---Basis "Link to this heading") ======================================================================================================================================================================================================= **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import datetime import os import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord import polars as pl Download L1 (book ticker) data to calculate the basis between spot and futures. \[3\]: start\_date \= datetime.datetime.strptime('20240901', '%Y%m%d') end\_date \= datetime.datetime.strptime('20241031', '%Y%m%d') tardis\_token \= "" \[4\]: def download\_from\_tardis(exchange, stream, symbol, start\_date, end\_date, output\_path, token): date \= start\_date while date <= end\_date: yyyymmdd \= date.strftime('%Y%m%d') year \= yyyymmdd\[:4\] month \= yyyymmdd\[4:6\] day \= yyyymmdd\[6:\] output\_file \= os.path.join(output\_path, f'{symbol}\_{yyyymmdd}.csv.gz') header \= f'"Authorization: Bearer {token}"' !wget \--header\={header} https://datasets.tardis.dev/v1/{exchange}/{stream}/{year}/{month}/{day}/{symbol}.csv.gz \-O {output\_file} date += datetime.timedelta(days\=1) \[5\]: download\_from\_tardis('binance', 'book\_ticker', 'BTCUSDT', start\_date, end\_date, 'spot/book\_ticker/BTCUSDT', tardis\_token) download\_from\_tardis('binance-futures', 'book\_ticker', 'BTCUSDT', start\_date, end\_date, 'usdm/book\_ticker/BTCUSDT', tardis\_token) Precompute the basis for faster backtesting. \[7\]: def load\_bookticker(file): return pl.read\_csv(file, schema\={ 'exchange': pl.String, 'symbol': pl.String, 'timestamp': pl.Int64, 'local\_timestamp': pl.Int64, 'ask\_amount': pl.Float64, 'ask\_price': pl.Float64, 'bid\_price': pl.Float64, 'bid\_amount': pl.Float64 }).with\_columns( pl.col('local\_timestamp').cast(pl.Datetime), mid\_price \= (.5 \* (pl.col('bid\_price') + pl.col('ask\_price'))), ).select(\['local\_timestamp', 'mid\_price'\]) def prepare\_px\_basis(spot\_file, futures\_file, sampling\_interval, rolling\_window): spot \= load\_bookticker(spot\_file) futures \= load\_bookticker(futures\_file) \# Resamples prices to calculate the basis. spot\_rs \= spot.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last() ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) futures\_rs \= futures.group\_by\_dynamic( index\_column\='local\_timestamp', every\=sampling\_interval ).agg( pl.col('mid\_price').last(), ).upsample( time\_column\='local\_timestamp', every\=sampling\_interval ).select(pl.all().forward\_fill()) return spot\_rs.join( futures\_rs, left\_on\='local\_timestamp', right\_on\='local\_timestamp', how\='full' ).with\_columns( rolling\_mean\_basis\=( pl.col('mid\_price\_right').forward\_fill() \- pl.col('mid\_price').forward\_fill() \# Computes the basis ).rolling\_mean(window\_size\=rolling\_window), \# Computes the moving average of the basis over the given window. ).select( local\_timestamp\=pl.col('local\_timestamp').dt.timestamp('ns'), spot\=pl.col('mid\_price'), basis\=pl.col('rolling\_mean\_basis') ) \[8\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_basis( f'spot/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000 \# 5-minute ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[9\]: np.savez\_compressed('px\_basis\_BTCUSDT\_5m', data\=precompute\_data) \[10\]: precompute\_data \= np.load('px\_basis\_BTCUSDT\_5m.npz')\['data'\] A market-making model based on the basis. Since the basis is often considered stationary, various time series analysis techniques, such as MA, AR, ARMA and etc, can be applied. Here, the simplest model, the Moving Average, is used for demonstration. This approach assumes that the basis will revert to the average of a given past period. \[12\]: @njit def basis\_mm( hbt, stat, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ): asset\_no \= 0 tick\_size \= hbt.depth(0).tick\_size lot\_size \= hbt.depth(0).lot\_size roi\_lb\_tick \= int(round(roi\_lb / tick\_size)) roi\_ub\_tick \= int(round(roi\_ub / tick\_size)) data\_i \= 0 last\_spot \= np.nan last\_basis \= np.nan while hbt.elapse(interval) \== 0: hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask mid\_price \= (best\_bid + best\_ask) / 2.0 #-------------------------------------------------------- \# Computes bid price and ask price. order\_qty \= max(round((order\_qty\_dollar / mid\_price) / lot\_size) \* lot\_size, lot\_size) normalized\_position \= position / order\_qty relative\_bid\_depth \= half\_spread + skew \* normalized\_position relative\_ask\_depth \= half\_spread \- skew \* normalized\_position \# Reads the latest observable spot price and basis from the precomputed data. while data\_i < len(precompute\_data): if precompute\_data\[data\_i, 0\] \> hbt.current\_timestamp: if data\_i \> 0: last\_spot \= precompute\_data\[data\_i \- 1, 1\] last\_basis \= precompute\_data\[data\_i \- 1, 2\] break data\_i += 1 \# Our fair price is calculated as the spot price + the rolling average of the basis fair\_px \= last\_spot + last\_basis bid\_price \= min(fair\_px \* (1.0 \- relative\_bid\_depth), best\_bid) ask\_price \= max(fair\_px \* (1.0 + relative\_ask\_depth), best\_ask) bid\_price \= np.floor(bid\_price / tick\_size) \* tick\_size ask\_price \= np.ceil(ask\_price / tick\_size) \* tick\_size #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price < max\_position\_dollar and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \* mid\_price \> \-max\_position\_dollar and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. stat.record(hbt) \[13\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size basis\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcusdt\_basis\_5m.npz') CPU times: user 1h 2min 41s, sys: 1min 45s, total: 1h 4min 27s Wall time: 40min 22s \[14\]: data \= np.load('stats/underlying\_btcusdt\_basis\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[14\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 3.280936 | 4.380048 | 0.05166 | 0.024406 | 537.702738 | 26.885072 | 2.116701 | 0.000032 | 1.0409e6 | \[15\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Basis_15_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Basis_15_0.png) On Binance, the BTCFDUSD spot market has a higher trading volume than the BTCUSDT spot market. BTCFDUSD records a daily trading volume of \\$3 billion, while BTCUSDT has \\$2.5 billion. Alternatively, you may consider using the exact index rather than a specific spot. You can find the weights composing the index using the API. [https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Composite-Index-Symbol-Information](https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Composite-Index-Symbol-Information) \[17\]: download\_from\_tardis('binance', 'book\_ticker', 'BTCFDUSD', start\_date, end\_date, 'spot/book\_ticker/BTCFDUSD', tardis\_token) \[18\]: data \= \[\] date \= start\_date while date <= end\_date: data.append(prepare\_px\_basis( f'spot/book\_ticker/BTCFDUSD/BTCFDUSD\_{date.strftime("%Y%m%d")}.csv.gz', f'usdm/book\_ticker/BTCUSDT/BTCUSDT\_{date.strftime("%Y%m%d")}.csv.gz', '100ms', 3000 \# 5-minute ).to\_numpy()) date += datetime.timedelta(days\=1) precompute\_data \= np.concatenate(data, axis\=0) \[19\]: np.savez\_compressed('px\_basis\_BTCFDUSD\_5m', data\=precompute\_data) \[20\]: precompute\_data \= np.load('px\_basis\_BTCFDUSD\_5m.npz')\['data'\] \[21\]: %%time roi\_lb \= 10000 roi\_ub \= 90000 latency\_data \= \[\] date \= start\_date while date <= end\_date: latency\_data.append('latency/order\_latency\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) data \= \[\] date \= start\_date while date <= end\_date: data.append('data2/btcusdt\_{}.npz'.format(date.strftime('%Y%m%d'))) date += datetime.timedelta(days\=1) asset \= ( BacktestAsset() .data(data) .initial\_snapshot('data2/btcusdt\_20240831\_eod.npz') .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(3) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.1) .lot\_size(0.001) .roi\_lb(roi\_lb) .roi\_ub(roi\_ub) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 60\_000\_000) half\_spread \= 0.0003 \# a ratio relative to the fair price skew \= half\_spread / 20 interval \= 100\_000\_000 \# in nanoseconds. 100ms order\_qty\_dollar \= 50\_000 max\_position\_dollar \= order\_qty\_dollar \* 20 grid\_num \= 1 grid\_interval \= hbt.depth(0).tick\_size basis\_mm( hbt, recorder.recorder, half\_spread, skew, precompute\_data, interval, order\_qty\_dollar, max\_position\_dollar, grid\_num, grid\_interval, roi\_lb, roi\_ub ) hbt.close() recorder.to\_npz('stats/underlying\_btcfdusd\_basis\_5m.npz') CPU times: user 1h 5min 24s, sys: 1min 50s, total: 1h 7min 14s Wall time: 42min 59s \[22\]: data \= np.load('stats/underlying\_btcfdusd\_basis\_5m.npz')\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats(book\_size\=1\_000\_000) ) stats.summary() \[22\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTurnover | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-09-01 00:00:00 | 2024-10-31 23:55:00 | 2.069684 | 2.647596 | 0.045228 | 0.047641 | 479.043661 | 23.952189 | 0.949337 | 0.000031 | 1.0376e6 | \[23\]: stats.plot() ![../_images/tutorials_Market_Making_with_Alpha_-_Basis_23_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Market_Making_with_Alpha_-_Basis_23_0.png) --- # Migration to v2 — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Migration to v2 * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/migration2.rst.txt) * * * Migration to v2[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/migration2.html#migration-to-v2 "Link to this heading") =========================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/migration2.html#overview "Link to this heading") ------------------------------------------------------------------------------------------------------------- The migration from version 1 to version 2 introduces several significant changes that can cause errors if the same code is used without modification. It is highly recommended to review the updated tutorials. This guide aims to help you avoid common pitfalls during the migration process. Checking Success: Use `elapse() == 0`[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/migration2.html#checking-success-use-elapse-0 "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------- In version 1, `elapse` function returns `True` on success and `False` otherwise. Typically, the strategy loop checks for successful elapsing using `while elapse(duration)`. However, in version 2, elapse returns a code instead of a boolean, with `0` indicating success and any other value indicating an error. Consequently, the code should be updated to check if the return value equals `0`. For instance: `while elapse(duration) == 0` If the code remains unchanged, it will fail because a return value of `0` (indicating success) will be treated as `False`. Other methods that involve elapsing, such as `submit_buy_order` or `submit_sell_order`, also return a code similar to `elapse` instead of a boolean. Ensure to check if their return values equal `0` to confirm success instead of checking for `True`. Data Format Changes[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/migration2.html#data-format-changes "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------- The data format fed into HftBacktest has undergone significant changes. It is strongly recommended to reprocess the data from raw data to preserve all information. However, if raw data is unavailable, [`the data conversion utility`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2 "hftbacktest.data.utils.migration2") from v1 to v2 is provided. The major changes are as follows: * SOA to AOS: The format has shifted from a columnar array (SOA) to a structured array (AOS). * Side Column Removal: `side` column has been removed. In version 2, the side is indicated by the `ev` field flags, [`BUY_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.BUY_EVENT "hftbacktest.types.BUY_EVENT") and [`SELL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.SELL_EVENT "hftbacktest.types.SELL_EVENT") . * Timestamp Handling: In version 1, the data utility corrects the event order by replacing one of the timestamps with `-1` to indicate an invalid event on either the exchange or the local side. In version 2, the validity of events on the exchange or local side is determined by ev field’s [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. * Timestamp Unit: Although not strictly enforced, the timestamp unit has changed from microseconds to nanoseconds. Additionally, the format for live order latency data has changed from SOA to AOS. --- # JIT Compilation Overhead — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * JIT Compilation Overhead * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/jit_compilation_overhead.rst.txt) * * * JIT Compilation Overhead[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/jit_compilation_overhead.html#jit-compilation-overhead "Link to this heading") =========================================================================================================================================================== HftBacktest takes advantage of Numba’s capabilities, relying on Numba JIT’ed classes. As a result, importing HftBacktest requires JIT compilation, which may take a few seconds. Additionally, the strategy function needs to be JIT’ed’ for performant backtesting, which also takes time to compile. Although this may not be significant when backtesting for multiple days, it can still be bothersome. To minimize this overhead, you can consider using Numba’s `cache` feature. See the example below. from numba import njit \# May take a few seconds from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest \# Enables caching feature @njit(cache\=True) def algo(arguments, hbt): \# your algo implementation. asset \= ( BacktestAsset() .linear\_asset(1.0) .data(\[\ 'data/ethusdt\_20221003.npz',\ 'data/ethusdt\_20221004.npz',\ 'data/ethusdt\_20221005.npz',\ 'data/ethusdt\_20221006.npz',\ 'data/ethusdt\_20221007.npz'\ \]) .initial\_snapshot('data/ethusdt\_20221002\_eod.npz') .no\_partial\_fill\_exchange() .intp\_order\_latency(\[\ 'data/latency\_20221003.npz',\ 'data/latency\_20221004.npz',\ 'data/latency\_20221005.npz',\ 'data/latency\_20221006.npz',\ 'data/latency\_20221007.npz'\ \]) .power\_prob\_queue\_model3(3.0) .tick\_size(0.01) .lot\_size(0.001) .trading\_value\_fee\_model(0.0002, 0.0007) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) algo(arguments, hbt) --- # Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/data.rst.txt) * * * Data[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/data.html#data "Link to this heading") =============================================================================================== Please see [Data Collector](https://github.com/nkaz001/hftbacktest/tree/master/collector) or [Data Preparation](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Data%20Preparation.html) regarding collecting and converting the feed data. Format[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/data.html#format "Link to this heading") --------------------------------------------------------------------------------------------------- hftbacktest can digest a numpy structured array. The data has 8 fields in the following order. You can also find details in [Event](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/struct.Event.html) . * ev (u64): You can find the possible flag combinations in [Constants](https://docs.rs/hftbacktest/0.3.1/hftbacktest/types/index.html#constants) . * exch\_ts (i64): Exchange timestamp, which is the time at which the event occurs on the exchange. * local\_ts (i64): Local timestamp, which is the time at which the event is received by the local. * px (f64): Price * qty (f64): Quantity * order\_id (u64): Order ID is only for the L3 Market-By-Order feed. * ival (i64): Reserved for an additional i64 value * faval (f64): Reserved for an additional f64 value **Raw data** > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22171.70 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22187.30 | 0.000 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.30 | 0.270 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22194.70 | 0.423 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22195.20 | 2.075 | 0 | 0 | 0.0 | | SELL\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22209.60 | 4.506 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205116000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | Validation[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/data.html#validation "Link to this heading") ----------------------------------------------------------------------------------------------------------- 1. All timestamps must be in the correct order, chronological order. There can be cases where an event happens before another at the exchange, resulting in an earlier exchange timestamp, but it is received locally after the other event. This reverses the chronological order of exchange and local timestamps. To handle this situation, hftbacktest uses the [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") and [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") flags. Events flagged with [`EXCH_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "hftbacktest.types.EXCH_EVENT") should be in chronological order according to the exchange timestamp, while events flagged with [`LOCAL_EVENT`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "hftbacktest.types.LOCAL_EVENT") should be in chronological order according to the local timestamp. 2. The exchange timestamp must be earlier than the local timestamp; the feed latency must be positive. Due to potential errors in time synchronization between two sites, the local timestamp may be earlier than the exchange timestamp, resulting in negative latency. The best way to address this is to improve time synchronization using PTP (Precision Time Protocol), which minimizes the possibility of negative latency. However, by adding a base latency or offsetting the size of the negative latency, you can ensure that the data remains valid with only positive latencies, where the local timestamp is always later than the exchange timestamp of the event. See the following example. The exchange timestamp of the depth feed is advanced to the prior trade feed even though the depth feed is received after the trade feed. > 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} This should be converted into the following form. HftBacktest provides [`correct_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#hftbacktest.data.correct_event_order "hftbacktest.data.correct_event_order") method to automatically correct this issue. [`validate_event_order`](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/data_validation.html#hftbacktest.data.validate_event_order "hftbacktest.data.validate_event_order") helps to check if this issue exists. > EXCH\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212385000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803051, 'p': '22177.90', 'q': '0.300', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212480000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206968, 'T': 1676419205111, 's': 'BTCUSDT', 't': 3288803052, 'p': '22177.90', 'q': '0.119', 'X': 'MARKET', 'm': True}} > LOCAL\_EVENT 1676419207212527000 {'stream': 'btcusdt@depth@0ms', 'data': {'e': 'depthUpdate', 'E': 1676419206974, 'T': 1676419205108, 's': 'BTCUSDT', 'U': 2505118837831, 'u': 2505118838224, 'pu': 2505118837821, 'b': \[\['2218.80', '0.603'\], \['5000.00', '2.641'\], \['22160.60', '0.008'\], \['22172.30', '0.551'\], \['22173.40', '0.073'\], \['22174.50', '0.006'\], \['22176.80', '0.157'\], \['22177.90', '0.425'\], \['22181.20', '0.260'\], \['22182.30', '3.918'\], \['22182.90', '0.000'\], \['22183.40', '0.014'\], \['22203.00', '0.000'\]\], 'a': \[\['22171.70', '0.000'\], \['22187.30', '0.000'\], \['22194.30', '0.270'\], \['22194.70', '0.423'\], \['22195.20', '2.075'\], \['22209.60', '4.506'\]\]}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212584000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803053, 'p': '22177.90', 'q': '0.001', 'X': 'MARKET', 'm': True}} > EXCH\_EVENT | LOCAL\_EVENT 1676419207212621000 {'stream': 'btcusdt@trade', 'data': {'e': 'trade', 'E': 1676419206976, 'T': 1676419205116, 's': 'BTCUSDT', 't': 3288803054, 'p': '22177.90', 'q': '0.005', 'X': 'MARKET', 'm': True}} **Normalized data** | ev | exch\_ts | local\_ts | px | qty | order\_id | ival | fval | | --- | --- | --- | --- | --- | --- | --- | --- | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| EXCH\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212385000 | 22177.90 | 0.300 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419205111000000 | 1676419207212480000 | 22177.90 | 0.119 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 2218.8 | 0.603 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 5000.00 | 2.641 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22160.60 | 0.008 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22172.30 | 0.551 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22173.40 | 0.073 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22174.50 | 0.006 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22176.80 | 0.157 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22177.90 | 0.425 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22181.20 | 0.260 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.30 | 3.918 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22182.90 | 0.000 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22183.40 | 0.014 | 0 | 0 | 0.0 | | BUY\_EVENT \| DEPTH\_EVENT \| LOCAL\_EVENT | 1676419205108000000 | 1676419207212527000 | 22203.00 | 0.000 | 0 | 0 | 0.0 | | … | | | | | | | | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212584000 | 22177.90 | 0.001 | 0 | 0 | 0.0 | | SELL\_EVENT \| TRADE\_EVENT \| EXCH\_EVENT \| LOCAL\_EVENT | 1676419206976000000 | 1676419207212621000 | 22177.90 | 0.005 | 0 | 0 | 0.0 | --- # Latency Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Latency Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/latency_models.rst.txt) * * * Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#latency-models "Link to this heading") ============================================================================================================================= Overview[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------- Latency is an important factor that you need to take into account when you backtest your HFT strategy. HftBacktest has three types of latencies. ![_images/latencies.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/latencies.png) * Feed latency This is the latency between the time the exchange sends the feed events such as order book change or trade and the time it is received by the local. This latency is dealt with through two different timestamps: local timestamp and exchange timestamp. * Order entry latency This is the latency between the time you send an order request and the time it is processed by the exchange’s matching engine. * Order response latency This is the latency between the time the exchange’s matching engine processes an order request and the time the order response is received by the local. The response to your order fill is also affected by this type of latency. ![_images/latency-comparison.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/latency-comparison.png) Order Latency Models[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#order-latency-models "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- HftBacktest provides the following order latency models and you can also implement your own latency model. ### ConstantLatency[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#constantlatency "Link to this heading") It’s the most basic model that uses constant latencies. You just set the latencies. You can find details below. * [ConstantLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.ConstantLatency.html) and [`constant_latency`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency "hftbacktest.BacktestAsset.constant_latency") ### IntpOrderLatency[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#intporderlatency "Link to this heading") This model interpolates order latency based on the actual order latency data. This is the most accurate among the provided models if you have the data with a fine time interval. You can collect the latency data by submitting unexecutable orders regularly. You can find details below. * [IntpOrderLatency](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/struct.IntpOrderLatency.html) and [`intp_order_latency`](https://hftbacktest.readthedocs.io/en/py-v2.2.0/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency "hftbacktest.BacktestAsset.intp_order_latency") **Data example** req\_ts (request timestamp at local), exch\_ts (exchange timestamp), resp\_ts (receipt timestamp at local), \_padding 1670026844751525000, 1670026844759000000, 1670026844762122000, 0 1670026845754020000, 1670026845762000000, 1670026845770003000, 0 ### FeedLatency[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#feedlatency "Link to this heading") If the live order latency data is unavailable, you can generate artificial order latency using feed latency. Please refer to [this tutorial](https://hftbacktest.readthedocs.io/en/py-v2.2.0/tutorials/Order%20Latency%20Data.html) for guidance. ### Implement your own order latency model[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/latency_models.html#implement-your-own-order-latency-model "Link to this heading") You need to implement the following trait. * [LatencyModel](https://docs.rs/hftbacktest/latest/hftbacktest/backtest/models/trait.LatencyModel.html) Please refer to [the latency model implementation](https://github.com/nkaz001/hftbacktest/blob/master/hftbacktest/src/backtest/models/latency.rs) . --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ============================================================================================================================================================================================================================================================================================================================ For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Statistics — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Statistics * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/reference/stats.rst.txt) * * * Statistics[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#statistics "Link to this heading") ====================================================================================================================== _class_ Stats[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/stats.html#Stats) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats "Link to this definition") **Example** import numpy as np from hftbacktest.stats import LinearAssetRecord asset0\_record \= np.load('backtest\_result.npz')\['0'\] stats \= ( LinearAssetRecord(asset0\_record) .resample('10s') .monthly() .stats(book\_size\=100000) ) stats.summary() stats.plot() Parameters: * **entire** (_DataFrame_) * **splits** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Mapping_](https://docs.python.org/3.10/library/typing.html#typing.Mapping "(in Python v3.10)")\ _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _,_ [_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _\]__\]_) * **kwargs** ([_Mapping_](https://docs.python.org/3.10/library/typing.html#typing.Mapping "(in Python v3.10)") _\[_[_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)")\ _,_ [_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _\]_) summary(_pretty\=False_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/stats.html#Stats.summary) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats.summary "Link to this definition") Displays the statistics summary. Parameters: **pretty** ([_bool_](https://docs.python.org/3.10/library/functions.html#bool "(in Python v3.10)") ) – Returns the statistics in a pretty-printed format. plot(_price\_as\_ret\=False_, _backend\='matplotlib'_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/stats.html#Stats.plot) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats.plot "Link to this definition") Plots the equity curves and positions over time along with the price chart. Parameters: * **price\_as\_ret** ([_bool_](https://docs.python.org/3.10/library/functions.html#bool "(in Python v3.10)") ) – Plots the price chart in cumulative returns if set to True; otherwise, it plots the price chart in raw price terms. * **backend** ([_Literal_](https://docs.python.org/3.10/library/typing.html#typing.Literal "(in Python v3.10)") _\[__'matplotlib'__,_ _'holoviews'__\]_) – Specifies which plotting library is used to plot the charts. The default is ‘matplotlib’. _class_ LinearAssetRecord(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/stats.html#LinearAssetRecord) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord "Link to this definition") Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__\_ScalarType\_co__\]__\]_ _|_ _DataFrame_) contract\_size(_contract\_size_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size "Link to this definition") Sets the contract size. The default value is 1.0. Parameters: **contract\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The asset’s contract size. Return type: Self daily()[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily "Link to this definition") Generates daily statistics. Return type: Self monthly()[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly "Link to this definition") Generates monthly statistics. Return type: Self resample(_frequency_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample "Link to this definition") Sets the resampling frequency for downsampling the record. This could affect the calculation of the metrics related to the sampling interval. Additionally, it reduces the time required for computing the metrics and plotting the charts. The default value is 10s. Parameters: **frequency** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Interval of the window. This internally uses Polars, please see [polars.DataFrame.group\_by\_dynamic](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.group_by_dynamic.html) for more details. Return type: Self stats(_metrics\=None_, _\*\*kwargs_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats "Link to this definition") **Examples** stats \= record.stats(\[SR('SR365', trading\_days\_per\_year\=365), AnnualRet(trading\_days\_per\_year\=365)\] Parameters: * **metrics** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _|_ [_Type_](https://docs.python.org/3.10/library/typing.html#typing.Type "(in Python v3.10)")\ _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _\]__\]_ _|_ _None_) – The metrics specified in this list will be computed for the record. Each metric should be a class derived from the Metric class. If the class type, instead of an instance, is specified, an instance of the class will be constructed with the provided `kwargs`. The default value is a list of [`SR`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.SR "hftbacktest.stats.metrics.SR") , [`Sortino`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Sortino "hftbacktest.stats.metrics.Sortino") , [`Ret`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Ret "hftbacktest.stats.metrics.Ret") , [`MaxDrawdown`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "hftbacktest.stats.metrics.MaxDrawdown") , [`DailyNumberOfTrades`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "hftbacktest.stats.metrics.DailyNumberOfTrades") , [`DailyTradingValue`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "hftbacktest.stats.metrics.DailyTradingValue") , [`ReturnOverMDD`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "hftbacktest.stats.metrics.ReturnOverMDD") , `ReturnOverTrade`, and [`MaxPositionValue`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "hftbacktest.stats.metrics.MaxPositionValue") . * **kwargs** ([_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)") ) – Keyword arguments that will be used to construct the Metric instance. Returns: The statistics for the specified metrics of the record. Return type: [_Stats_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.stats.Stats") time\_unit(_time\_unit_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit "Link to this definition") Sets the time unit for converting timestamps in the records to datetime. The default value is ns. Parameters: **time\_unit** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – The unit of time of the timesteps since epoch time. This internally uses Polars, please see [polars.from\_epoch](https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.from_epoch.html) for more details. Return type: Self _class_ InverseAssetRecord(_data_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/stats.html#InverseAssetRecord) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord "Link to this definition") Parameters: **data** ([_ndarray_](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "(in NumPy v2.0)") _\[_[_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)")\ _,_ [_dtype_](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype "(in NumPy v2.0)")\ _\[__\_ScalarType\_co__\]__\]_ _|_ _DataFrame_) contract\_size(_contract\_size_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size "Link to this definition") Sets the contract size. The default value is 1.0. Parameters: **contract\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The asset’s contract size. Return type: Self daily()[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily "Link to this definition") Generates daily statistics. Return type: Self monthly()[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly "Link to this definition") Generates monthly statistics. Return type: Self resample(_frequency_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample "Link to this definition") Sets the resampling frequency for downsampling the record. This could affect the calculation of the metrics related to the sampling interval. Additionally, it reduces the time required for computing the metrics and plotting the charts. The default value is 10s. Parameters: **frequency** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Interval of the window. This internally uses Polars, please see [polars.DataFrame.group\_by\_dynamic](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.group_by_dynamic.html) for more details. Return type: Self stats(_metrics\=None_, _\*\*kwargs_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats "Link to this definition") **Examples** stats \= record.stats(\[SR('SR365', trading\_days\_per\_year\=365), AnnualRet(trading\_days\_per\_year\=365)\] Parameters: * **metrics** ([_List_](https://docs.python.org/3.10/library/typing.html#typing.List "(in Python v3.10)") _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _|_ [_Type_](https://docs.python.org/3.10/library/typing.html#typing.Type "(in Python v3.10)")\ _\[_[_Metric_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric "hftbacktest.stats.metrics.Metric")\ _\]__\]_ _|_ _None_) – The metrics specified in this list will be computed for the record. Each metric should be a class derived from the Metric class. If the class type, instead of an instance, is specified, an instance of the class will be constructed with the provided `kwargs`. The default value is a list of [`SR`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.SR "hftbacktest.stats.metrics.SR") , [`Sortino`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Sortino "hftbacktest.stats.metrics.Sortino") , [`Ret`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Ret "hftbacktest.stats.metrics.Ret") , [`MaxDrawdown`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "hftbacktest.stats.metrics.MaxDrawdown") , [`DailyNumberOfTrades`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "hftbacktest.stats.metrics.DailyNumberOfTrades") , [`DailyTradingValue`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "hftbacktest.stats.metrics.DailyTradingValue") , [`ReturnOverMDD`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "hftbacktest.stats.metrics.ReturnOverMDD") , `ReturnOverTrade`, and [`MaxPositionValue`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "hftbacktest.stats.metrics.MaxPositionValue") . * **kwargs** ([_Any_](https://docs.python.org/3.10/library/typing.html#typing.Any "(in Python v3.10)") ) – Keyword arguments that will be used to construct the Metric instance. Returns: The statistics for the specified metrics of the record. Return type: [_Stats_](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.stats.Stats") time\_unit(_time\_unit_)[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit "Link to this definition") Sets the time unit for converting timestamps in the records to datetime. The default value is ns. Parameters: **time\_unit** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – The unit of time of the timesteps since epoch time. This internally uses Polars, please see [polars.from\_epoch](https://docs.pola.rs/api/python/stable/reference/expressions/api/polars.from_epoch.html) for more details. Return type: Self Metrics[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#metrics "Link to this heading") ---------------------------------------------------------------------------------------------------------------- _class_ Metric[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#Metric) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Metric "Link to this definition") A base class for computing a strategy’s performance metrics. Implementing a custom metric class derived from this base class enables the computation of the custom metric in the [`Stats`](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Stats "hftbacktest.stats.Stats") and displays the summary. _class_ Ret(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#Ret) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Ret "Link to this definition") Return Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is Return. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. _class_ AnnualRet(_name\=None_, _book\_size\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#AnnualRet) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.AnnualRet "Link to this definition") Annualised return Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is AnnualReturn. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – The number of trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. _class_ SR(_name\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#SR) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.SR "Link to this definition") Sharpe Ratio without considering a benchmark. Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is SR. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – Trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. Additionally, be aware that to compute the daily Sharpe Ratio, it also multiplies by sqrt(the sample number per day), so the computed Sharpe Ratio is affected by the sampling interval. _class_ Sortino(_name\=None_, _trading\_days\_per\_year\=252_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#Sortino) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.Sortino "Link to this definition") Sortino Ratio without considering a benchmark. Parameters: * **name** – Name of this metric. The default value is Sortino. * **trading\_days\_per\_year** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) – Trading days per year to annualise. Commonly, 252 is used in trad-fi, so the default value is 252 to match that scale. However, you can use 365 instead of 252 for crypto markets, which run 24/7. Additionally, be aware that to compute the daily Sharpe Ratio, it also multiplies by sqrt(the sample number per day), so the computed Sharpe Ratio is affected by the sampling interval. _class_ MaxDrawdown(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#MaxDrawdown) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxDrawdown "Link to this definition") Maximum Drawdown Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is MaxDrawdown. * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) – If the book size, or capital allocation, is set, the metric is divided by the book size to express it as a percentage ratio of the book size; otherwise, the metric is in raw units. _class_ ReturnOverMDD(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#ReturnOverMDD) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverMDD "Link to this definition") Return over Maximum Drawdown Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is ReturnOverMDD. _class_ ReturnOverTrade(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#ReturnOverTrade) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.ReturnOverTrade "Link to this definition") Return over Trade value, which represents the profit made per unit of trading value, for instance, $profit / $trading\_value. Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) – Name of this metric. The default value is ReturnOverTrade. _class_ NumberOfTrades(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#NumberOfTrades) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.NumberOfTrades "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ DailyNumberOfTrades(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#DailyNumberOfTrades) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ TradingVolume(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#TradingVolume) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.TradingVolume "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ DailyTradingVolume(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#DailyTradingVolume) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingVolume "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ TradingValue(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#TradingValue) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.TradingValue "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) _class_ DailyTradingValue(_name\=None_, _book\_size\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#DailyTradingValue) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.DailyTradingValue "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") _|_ _None_) _class_ MaxPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#MaxPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MeanPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#MeanPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MeanPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MedianPositionValue(_name\=None_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#MedianPositionValue) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MedianPositionValue "Link to this definition") Parameters: **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) _class_ MaxLeverage(_name\=None_, _book\_size\=0.0_)[\[source\]](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_modules/hftbacktest/stats/metrics.html#MaxLeverage) [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/reference/stats.html#hftbacktest.stats.MaxLeverage "Link to this definition") Parameters: * **name** ([_str_](https://docs.python.org/3.10/library/stdtypes.html#str "(in Python v3.10)") ) * **book\_size** ([_float_](https://docs.python.org/3.10/library/functions.html#float "(in Python v3.10)") ) --- # Probability Queue Position Models — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.0.0/index.html) * Probability Queue Position Models * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_sources/tutorials/Probability%20Queue%20Models.ipynb.txt) * * * Probability Queue Position Models[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Probability%20Queue%20Models.html#Probability-Queue-Position-Models "Link to this heading") =========================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.0.0/tutorials/Probability%20Queue%20Models.html#Overview "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------- Here, we will demonstrate how queue position models affect order fill simulation and, ultimately, the strategy’s performance. It is essential for accurate backtesting to find the proper queue position modeling by comparing backtest and real trading results. In this context, we will illustrate comparisons by changing queue position models. By doing this, you can determine the appropriate queue position model that aligns with the backtesting and real trading results. **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. \[1\]: import numpy as np from numba import njit, uint64 from numba.typed import Dict from hftbacktest import ( BacktestAsset, ROIVectorMarketDepthBacktest, GTX, LIMIT, BUY, SELL, BUY\_EVENT, SELL\_EVENT, Recorder ) from hftbacktest.stats import LinearAssetRecord @njit(cache\=True) def measure\_trading\_intensity(order\_arrival\_depth, out): max\_tick \= 0 for depth in order\_arrival\_depth: if not np.isfinite(depth): continue \# Sets the tick index to 0 for the nearest possible best price \# as the order arrival depth in ticks is measured from the mid-price tick \= round(depth / .5) \- 1 \# In a fast-moving market, buy trades can occur below the mid-price (and vice versa for sell trades) \# since the mid-price is measured in a previous time-step; \# however, to simplify the problem, we will exclude those cases. if tick < 0 or tick \>= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= (w \* sxy \- sx \* sy) / (w \* sx2 \- sx\*\*2) intercept \= (sy \- slope \* sx) / w return slope, intercept @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[2\]: def backtest(args): asset\_name, asset\_info, model \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) if model \== 'SquareProbQueueModel': asset.power\_prob\_queue\_model(2) elif model \== 'LogProbQueueModel2': asset.log\_prob\_queue\_model2() elif model \== 'PowerProbQueueModel3': asset.power\_prob\_queue\_model3(3) else: raise ValueError hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name)) \[3\]: %%capture from multiprocessing import Pool import json with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'SquareProbQueueModel') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'LogProbQueueModel2') for k, v in assets.items()\])) with Pool(16) as p: print(p.map(backtest, \[(k, v, 'PowerProbQueueModel3') for k, v in assets.items()\])) \[4\]: import polars as pl from matplotlib import pyplot as plt def compute\_net\_equity(model): equity\_values \= {} sr\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_qm\_{}\_{}.npz'.format(model, asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number \== 100: \# 5\_000 is capital for each trading asset. return net\_equity.with\_columns( (pl.col('equity') / asset\_number / 5\_000).alias('equity') ) np.seterr(divide\='ignore', invalid\='ignore') fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] for model in \['SquareProbQueueModel', 'LogProbQueueModel2', 'PowerProbQueueModel3'\]: net\_equity\_ \= compute\_net\_equity(model) pnl \= net\_equity\_\['equity'\].diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('100 assets, Daily SR={:.2f}, {}'.format(sr, model)) plt.plot(net\_equity\_\['timestamp'\], net\_equity\_\['equity'\] \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[4\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Probability_Queue_Models_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.0.0/_images/tutorials_Probability_Queue_Models_4_1.png) --- # Index — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.4.2/index.html) * Index * * * Index ===== [**A**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#A) | [**B**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#B) | [**C**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#C) | [**D**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#D) | [**E**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#E) | [**F**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#F) | [**G**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#G) | [**H**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#H) | [**I**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#I) | [**L**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#L) | [**M**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#M) | [**N**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#N) | [**O**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#O) | [**P**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#P) | [**Q**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#Q) | [**R**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#R) | [**S**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#S) | [**T**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#T) | [**U**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#U) | [**V**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#V) | [**W**](https://hftbacktest.readthedocs.io/en/py-v2.4.2/genindex.html#W) A - | | | | --- | --- | | * [ALL\_ASSETS (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.ALL_ASSETS)

* [AnnualRet (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.AnnualRet) | * [ask\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_depth)

* [ask\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.ask_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.ask_qty_at_tick) | B - | | | | --- | --- | | * [BacktestAsset (class in hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset)

* [balance (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.balance)

* [best\_ask (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask)

* [best\_ask\_qty (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_qty)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_qty)

* [best\_ask\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_ask_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_ask_tick)

* [best\_bid (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid) | * [best\_bid\_qty (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_qty)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_qty)

* [best\_bid\_tick (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.best_bid_tick)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.best_bid_tick)

* [bid\_depth (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_depth)

* [bid\_qty\_at\_tick() (HashMapMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.bid_qty_at_tick)
* [(ROIVectorMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.bid_qty_at_tick)

* [BUY (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.BUY)

* [BUY\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.BUY_EVENT) | C - | | | | --- | --- | | * [cancel() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.cancel)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.cancel)

* [CANCELED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.CANCELED)

* [cancellable (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.cancellable)

* [class\_type (DiffOrderBookSnapshot attribute)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot.class_type)

* [clear\_inactive\_orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_inactive_orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_inactive_orders)

* [clear\_last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.clear_last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.clear_last_trades)

* [close() (FuseMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.close)
* [(HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.close)

* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.close)

* [constant\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.constant_latency)

* [constant\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.constant_order_latency) | * [contract\_size() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.contract_size)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.contract_size)

* [convert() (in module hftbacktest.data.utils.binancefutures)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancefutures.html#hftbacktest.data.utils.binancefutures.convert)
* [(in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert)

* [(in module hftbacktest.data.utils.databento)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.databento.html#hftbacktest.data.utils.databento.convert)

* [(in module hftbacktest.data.utils.migration2)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.migration2.html#hftbacktest.data.utils.migration2.convert)

* [(in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert)

* [convert\_fuse() (in module hftbacktest.data.utils.tardis)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.tardis.html#hftbacktest.data.utils.tardis.convert_fuse)

* [convert\_snapshot() (in module hftbacktest.data.utils.binancehistmktdata)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancehistmktdata.html#hftbacktest.data.utils.binancehistmktdata.convert_snapshot)

* [correct\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#hftbacktest.data.correct_event_order)

* [correct\_local\_timestamp() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#hftbacktest.data.correct_local_timestamp)

* [create\_last\_snapshot() (in module hftbacktest.data.utils.snapshot)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.snapshot.html#hftbacktest.data.utils.snapshot.create_last_snapshot)

* [current\_timestamp (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.current_timestamp)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.current_timestamp) | D - | | | | --- | --- | | * [daily() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.daily)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.daily)

* [DailyNumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.DailyNumberOfTrades)

* [DailyTradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.DailyTradingValue)

* [DailyTradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.DailyTradingVolume)

* [data() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.data) | * [depth() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.depth)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.depth)

* [DEPTH\_CLEAR\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT)

* [DEPTH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.DEPTH_EVENT)

* [DEPTH\_SNAPSHOT\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT)

* [DiffOrderBookSnapshot (class in hftbacktest.data.utils.difforderbooksnapshot)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.difforderbooksnapshot.html#hftbacktest.data.utils.difforderbooksnapshot.DiffOrderBookSnapshot) | E - | | | | --- | --- | | * [elapse() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse)

* [elapse\_bt() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.elapse_bt)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.elapse_bt)

* [EXCH\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.EXCH_EVENT) | * [exch\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.exch_timestamp)

* [exec\_price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.exec_price)

* [exec\_price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.exec_price_tick)

* [exec\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.exec_qty)

* [EXPIRED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.EXPIRED) | F - | | | | --- | --- | | * [fee (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.fee)

* [feed\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.feed_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.feed_latency)

* [FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.FILLED) | * [flat\_per\_trade\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.flat_per_trade_fee_model)

* [FOK (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.FOK)

* [fused\_events (FuseMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.fused_events)

* [FuseMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth)
* [(class in hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#hftbacktest.data.FuseMarketDepth) | G - | | | | --- | --- | | * [get() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.OrderDict.get) | * [GTC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.GTC)

* [GTX (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.GTX) | H - | | | | --- | --- | | * [HashMapMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth)

* [HashMapMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest)

* [HashMapMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.HashMapMarketDepthBacktest)

* hftbacktest.data
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#module-hftbacktest.data)

* hftbacktest.data.utils.binancefutures
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* hftbacktest.data.utils.binancehistmktdata
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata) | * hftbacktest.data.utils.databento
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* hftbacktest.data.utils.difforderbooksnapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* hftbacktest.data.utils.migration2
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* hftbacktest.data.utils.snapshot
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* hftbacktest.data.utils.tardis
* [module](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | I - | | | | --- | --- | | * [initial\_snapshot() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.initial_snapshot)

* [intp\_order\_latency() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.intp_order_latency) | * [inverse\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.inverse_asset)

* [InverseAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord)

* [IOC (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.IOC) | L - | | | | --- | --- | | * [l3\_fifo\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.l3_fifo_queue_model)

* [last\_trades() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.last_trades)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.last_trades)

* [last\_trades\_capacity() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.last_trades_capacity)

* [latency\_offset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.latency_offset)

* [leaves\_qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.leaves_qty)

* [LIMIT (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.LIMIT)

* [linear\_asset() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.linear_asset) | * [LinearAssetRecord (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord)

* [LOCAL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.LOCAL_EVENT)

* [local\_timestamp (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.local_timestamp)

* [log\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model)

* [log\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.log_prob_queue_model2)

* [lot\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.lot_size)
* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.lot_size)

* [lot\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.lot_size) | M - | | | | --- | --- | | * [MARKET (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.MARKET)

* [MaxDrawdown (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.MaxDrawdown)

* [MaxLeverage (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.MaxLeverage)

* [MaxPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.MaxPositionValue)

* [MeanPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.MeanPositionValue)

* [MedianPositionValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.MedianPositionValue)

* [Metric (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Metric)

* [modify() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.modify)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.modify)

* module
* [hftbacktest.data](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#module-hftbacktest.data)

* [hftbacktest.data.utils.binancefutures](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancefutures.html#module-hftbacktest.data.utils.binancefutures)

* [hftbacktest.data.utils.binancehistmktdata](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.binancehistmktdata.html#module-hftbacktest.data.utils.binancehistmktdata)

* [hftbacktest.data.utils.databento](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.databento.html#module-hftbacktest.data.utils.databento)

* [hftbacktest.data.utils.difforderbooksnapshot](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.difforderbooksnapshot.html#module-hftbacktest.data.utils.difforderbooksnapshot)

* [hftbacktest.data.utils.migration2](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.migration2.html#module-hftbacktest.data.utils.migration2)

* [hftbacktest.data.utils.snapshot](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.snapshot.html#module-hftbacktest.data.utils.snapshot)

* [hftbacktest.data.utils.tardis](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/hftbacktest.data.utils.tardis.html#module-hftbacktest.data.utils.tardis) | * [monthly() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.monthly)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.monthly) | N - | | | | --- | --- | | * [NEW (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.NEW)

* [no\_partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.no_partial_fill_exchange)

* [NONE (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.NONE) | * [num\_assets (HashMapMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.num_assets)
* [(ROIVectorMarketDepthBacktest property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.num_assets)

* [num\_trades (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.num_trades)

* [NumberOfTrades (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.NumberOfTrades) | O - | | | | --- | --- | | * [Order (class in hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order)

* [order\_id (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.order_id)

* [order\_latency() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.order_latency)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.order_latency) | * [order\_type (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.order_type)

* [OrderDict (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.OrderDict)

* [orders() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.orders)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.orders) | P - | | | | --- | --- | | * [parallel\_load() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.parallel_load)

* [partial\_fill\_exchange() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.partial_fill_exchange)

* [PARTIALLY\_FILLED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED)

* [plot() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Stats.plot)

* [position (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.position)

* [position() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.position)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.position) | * [power\_prob\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model)

* [power\_prob\_queue\_model2() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model2)

* [power\_prob\_queue\_model3() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.power_prob_queue_model3)

* [price (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.price)

* [price\_tick (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.price_tick)

* [process\_event() (FuseMarketDepth method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_utilities.html#hftbacktest.binding.FuseMarketDepth.process_event) | Q - * [qty (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.qty) R - | | | | --- | --- | | * [REJECTED (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.REJECTED)

* [req (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.req)

* [resample() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.resample)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.resample)

* [Ret (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Ret)

* [ReturnOverMDD (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.ReturnOverMDD)

* [ReturnOverTrade (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.ReturnOverTrade) | * [risk\_adverse\_queue\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.risk_adverse_queue_model)

* [roi\_lb() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.roi_lb)

* [roi\_lb\_tick (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.roi_lb_tick)

* [roi\_ub() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.roi_ub)

* [roi\_ub\_tick (ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.roi_ub_tick)

* [ROIVectorMarketDepth (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth)

* [ROIVectorMarketDepthBacktest (class in hftbacktest.binding)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest)

* [ROIVectorMarketDepthBacktest() (in module hftbacktest)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.ROIVectorMarketDepthBacktest) | S - | | | | --- | --- | | * [SELL (in module hftbacktest.order)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.order.SELL)

* [SELL\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.SELL_EVENT)

* [side (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.side)

* [Sortino (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Sortino)

* [SR (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.SR)

* [state\_values() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.state_values)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.state_values)

* [StateValues (class in hftbacktest.state)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues) | * [Stats (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Stats)

* [stats() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.stats)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.stats)

* [status (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.status)

* [submit\_buy\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_buy_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_buy_order)

* [submit\_sell\_order() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.submit_sell_order)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.submit_sell_order)

* [summary() (Stats method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.Stats.summary) | T - | | | | --- | --- | | * [tick\_size (HashMapMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepth.tick_size)
* [(Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.tick_size)

* [(ROIVectorMarketDepth property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepth.tick_size)

* [tick\_size() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.tick_size)

* [time\_in\_force (Order property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.order.Order.time_in_force)

* [time\_unit() (InverseAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.InverseAssetRecord.time_unit)
* [(LinearAssetRecord method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.LinearAssetRecord.time_unit) | * [TRADE\_EVENT (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.TRADE_EVENT)

* [trading\_qty\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.trading_qty_fee_model)

* [trading\_value (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.trading_value)

* [trading\_value\_fee\_model() (BacktestAsset method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/initialization.html#hftbacktest.BacktestAsset.trading_value_fee_model)

* [trading\_volume (StateValues property)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.state.StateValues.trading_volume)

* [TradingValue (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.TradingValue)

* [TradingVolume (class in hftbacktest.stats)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/stats.html#hftbacktest.stats.TradingVolume) | U - * [UNTIL\_END\_OF\_DATA (in module hftbacktest.types)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA) V - | | | | --- | --- | | * [validate\_event\_order() (in module hftbacktest.data)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/data_validation.html#hftbacktest.data.validate_event_order) | * [values() (OrderDict method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.OrderDict.values) | W - | | | | --- | --- | | * [wait\_next\_feed() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_next_feed)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_next_feed) | * [wait\_order\_response() (HashMapMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.HashMapMarketDepthBacktest.wait_order_response)
* [(ROIVectorMarketDepthBacktest method)](https://hftbacktest.readthedocs.io/en/py-v2.4.2/reference/backtester.html#hftbacktest.binding.ROIVectorMarketDepthBacktest.wait_order_response) | --- # Risk Mitigation through Price Protection in Extreme Market Conditions — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.4.2/index.html) * Risk Mitigation through Price Protection in Extreme Market Conditions * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.4.2/_sources/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.ipynb.txt) * * * Risk Mitigation through Price Protection in Extreme Market Conditions[](https://hftbacktest.readthedocs.io/en/py-v2.4.2/tutorials/Risk%20Mitigation%20through%20Price%20Protection%20in%20Extreme%20Market%20Conditions.html#Risk-Mitigation-through-Price-Protection-in-Extreme-Market-Conditions "Link to this heading") ============================================================================================================================================================================================================================================================================================================================ For high-frequency traders and market makers, latency plays a crucial role in maintaining profitability. However, in the cryptocurrency market especially, significant price movements and delayed market updates are common occurrences. To safeguard your quotes and positions against these unfavorable conditions, it is essential to employ price protection mechanisms akin to those offered by Binance. [https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc](https://www.binance.com/en/support/faq/how-to-enable-the-price-protection-function-2b9dc811ce7340469357867122b975dc) > Price Protection is another function offered by Binance Futures to protect traders from extreme market movements. This function protects traders from bad actors who exploit market efficiencies and cause price manipulation. > > The Price Protection feature is helpful against unusual market conditions, such as a large difference between the Last Price and Mark Price. Usually, the Mark Price is just a few cents away from the Last Price. However, in extreme market conditions, the Last Price may significantly deviate from the Mark Price. As highlighted by Binance, substantial disparities between futures prices and their underlying spot prices may signal extreme market conditions. This can be mitigated by employing conservative pricing strategies, such as setting the minimum bid price for futures and their underlying spots and the maximum ask price for futures and their underlying spots. Additionally, detecting abnormalities in the price discrepancy between futures and underlying spot prices can prompt exiting positions and awaiting a return to normal market conditions. Furthermore, it is necessary to carefully monitor latency, including both feed latency and order latency, as it prevents the tracking of market prices and hinders timely adjustments to orders. In extreme market conditions, latency spikes often occur and may impede price protection, making it advisable to withdraw from the market in such situations. Example to be added… --- # Debugging Backtesting and Live Discrepancies — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Debugging Backtesting and Live Discrepancies * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/debugging_backtesting_and_live_discrepancies.rst.txt) * * * Debugging Backtesting and Live Discrepancies[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/debugging_backtesting_and_live_discrepancies.html#debugging-backtesting-and-live-discrepancies "Link to this heading") ======================================================================================================================================================================================================================= Plotting both live and backtesting values on a single chart is a good initial step. It’s strongly recommended to include the equity curve and position plots for comparison purposes. Additionally, visualizing your alpha, order prices, etc can facilitate the identification of discrepancies. \[Image\] If the backtested strategy is correctly implemented in live trading, two significant factors may contribute to any observed discrepancies. 1\. Latency: Latency, encompassing both feed and order latency, plays a crucial role in ensuring accurate backtesting results. It’s highly recommended to collect data yourself to accurately measure feed latency on your end. Alternatively, if obtaining data from external sources, it’s essential to verify that the feed latency aligns with your latency. Order latency, measured from your end, can be collected by logging order actions or regularly submitting orders away from the mid-price and subsequently canceling them to measure and record order latency. It’s still possible to artificially decrease latencies to assess improvements in strategy performance due to enhanced latency. This allows you to evaluate the effectiveness of higher-tier programs or liquidity provider programs, as well as quantify the impact of investments made in infrastructure improvement. Understanding whether a superior infrastructure provides a competitive advantage is beneficial. 2\. Queue Model: Selecting an appropriate queue model that accurately reflects live trading results is essential. You can either develop your own queue model or utilize existing ones. Hftbacktest offers three primary queue models such as `PowerProbQueueModel` series, allowing for adjustments to align with your results. For further information, refer to [ProbQueueModel](https://hftbacktest.readthedocs.io/en/py-v2.2.0/order_fill.html#order-fill-prob-queue-model) . One crucial point to bear in mind is the backtesting conducted under the assumption of no market impact. A market order, or a limit order that take liquidity, can introduce discrepancies, as it may cause market impact and consequently make execution simulation difficult. Moreover, if your limit order size is too large, partial fills and their market impact can also lead to discrepancies. It’s advisable to begin trading with a small size and align the results first. Gradually increasing your trading size while observing both live and backtesting results is recommended. --- # Making Multiple Markets - Introduction — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Making Multiple Markets - Introduction * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/tutorials/Making%20Multiple%20Markets%20-%20Introduction.ipynb.txt) * * * Making Multiple Markets - Introduction[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/tutorials/Making%20Multiple%20Markets%20-%20Introduction.html#Making-Multiple-Markets---Introduction "Link to this heading") ======================================================================================================================================================================================================================= One of the core concepts of quantitative trading is to create a portfolio by combining multiple assets or strategies to diversify risks. By combining multiple strategies, you can obtain a less volatile portfolio return. In other words, you can achieve a higher Sharpe ratio by combining multiple assets or strategies. Even if your individual strategy’s Sharpe ratio is low, constructing a portfolio with multiple assets or strategies can result in a higher Sharpe ratio for the combined portfolio. You can see how this works with the following straightforward example, without complex mathematics. \[1\]: import numpy as np from matplotlib import pyplot as plt def compute\_equity(returns, intial\_equity, bet\_size): return intial\_equity + np.cumsum(bet\_size \* returns, axis\=0) mean \= 0.001 std \= 0.05 risk\_free\_rate \= 0.04 / 252 sharpe\_ratio \= (mean \- risk\_free\_rate) / std \* np.sqrt(252) print(f'The Sharpe Ratio for each individual strategy or asset: {sharpe\_ratio:.2f}') num\_periods \= 252 intial\_equity \= 10000 bet\_size \= 10000 num\_assets\_or\_num\_strat \= 1000 \# Generates series of random returns with a normal distribution. returns \= np.random.normal(mean, std, (num\_periods, num\_assets\_or\_num\_strat)) \# Initializes the starting point at zero. returns\[0, :\] \= 0 equity\_series \= compute\_equity(returns, intial\_equity, bet\_size) The Sharpe Ratio for each individual strategy or asset: 0.27 Here, it creates a series of random returns with a low target Sharpe ratio. In the following graphs, it is difficult to determine if the individual strategy is effective. \[2\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_3_0.png) \[3\]: for i in np.random.randint(num\_assets\_or\_num\_strat, size\=5): plt.figure(i, figsize\=(10, 5)) plt.title(f'#{i} Equity curve') plt.xlabel('Time') plt.ylabel('$') plt.plot(equity\_series\[:, i\]) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_0.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_1.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_2.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_3.png) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_4_4.png) However, by combining multiple individual assets or strategies into a portfolio and plotting the portfolio’s equity curve and Sharpe ratio, you can observe a higher Sharpe ratio and a more linear equity curve as you combine more. The more assets or strategies are combined, the higher the Sharpe ratio becomes. \[4\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[4\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_6_1.png) \[5\]: sharpe\_ratio \= \[\] plt.figure(figsize\=(10, 5)) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size plt.plot(portfolio\_equity) plt.title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') plt.xlabel('Time') plt.ylabel('$') sr \= (np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252) print(f'Sharpe ratio of a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies: {sr:.2f}') Sharpe ratio of a portfolio combining all 1000 assets or strategies: 6.88 ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_7_1.png) One important factor to consider is **the correlation** of returns between assets or strategies. The higher the correlation, the less effective the portfolio will be. \[6\]: def generate\_correlated\_returns(num\_periods, correlation, mean, std, num): uncorrelated\_returns \= np.random.normal(mean, std, (num, num\_periods)) corr\_matrix \= np.ones((num, num), np.float64) \* correlation for i in range(num): corr\_matrix\[i, i\] \= 1.0 L \= np.linalg.cholesky(corr\_matrix) correlated\_returns \= np.dot(L, uncorrelated\_returns) return np.transpose(correlated\_returns) \[7\]: correlation \= 0.25 ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) \[8\]: plt.figure(figsize\=(10, 5)) plt.title('Equity curves for all individual assets and strategies') plt.xlabel('Time') plt.ylabel('$') \_ \= plt.plot(equity\_series) ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_11_0.png) \[9\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for sum\_num\_assets\_or\_num\_strat in \[10, 50, 100, 200, 500, 1000\]: \# Normalizes by dividing by the number of combined assets or strategies. portfolio\_equity \= np.sum(equity\_series\[:, :sum\_num\_assets\_or\_num\_strat\], axis\=1) / sum\_num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'#{sum\_num\_assets\_or\_num\_strat} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title('Equity curves for a portfolio with multiple assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[9\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_12_1.png) \[10\]: sharpe\_ratio \= \[\] fig \= plt.figure(figsize\=(10, 5)) ax \= plt.subplot(111) for correlation in \[0.1, 0.2, 0.3, 0.5, 0.7, 0.9\]: ret \= generate\_correlated\_returns(num\_periods, correlation, mean, std, num\_assets\_or\_num\_strat) \# Initializes the starting point at zero. ret\[0, :\] \= 0 equity\_series \= compute\_equity(ret, intial\_equity, bet\_size) portfolio\_equity \= np.sum(equity\_series, axis\=1) / num\_assets\_or\_num\_strat ret \= np.diff(portfolio\_equity) / bet\_size ax.plot(portfolio\_equity) sharpe\_ratio.append(f'Corr: {correlation} SR: {(np.mean(ret) \- risk\_free\_rate) / np.std(ret) \* np.sqrt(252):.2f}') ax.set\_title(f'Equity curve for a portfolio combining all {num\_assets\_or\_num\_strat} assets or strategies') ax.set\_xlabel('Time') ax.set\_ylabel('$') ax.legend(sharpe\_ratio, loc\='upper right', bbox\_to\_anchor\=(1.25, 1)) \[10\]: ![../_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_images/tutorials_Making_Multiple_Markets_-_Introduction_13_1.png) --- # Order Latency Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.2.0/index.html) * Order Latency Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.2.0/_sources/tutorials/Order%20Latency%20Data.ipynb.txt) * * * Order Latency Data[](https://hftbacktest.readthedocs.io/en/py-v2.2.0/tutorials/Order%20Latency%20Data.html#Order-Latency-Data "Link to this heading") ======================================================================================================================================================= To obtain more realistic backtesting results, accounting for latencies is crucial. Therefore, it’s important to collect both feed data and order data with timestamps to measure your order latency. The best approach is to gather your own order latencies. You can collect order latency based on your live trading or by regularly submitting orders at a price that cannot be filled and then canceling them for recording purposes. However, if you don’t have access to them or want to establish a target, you will need to artificially generate order latency. You can model this latency based on factors such as feed latency, trade volume, and the number of events. In this guide, we will demonstrate a simple method to generate order latency from feed latency using a multiplier and offset for adjustment. First, loads the feed data. \[1\]: import numpy as np data \= np.load('btcusdt\_20200201.npz')\['data'\] data \[1\]: array(\[(3758096386, 1580515202342000000, 1580515202497052000, 9364.51, 1.197, 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497346000, 9365.67, 0.02 , 0, 0, 0.),\ (3758096386, 1580515202342000000, 1580515202497352000, 9365.86, 0.01 , 0, 0, 0.),\ ...,\ (3489660929, 1580601599836000000, 1580601599962961000, 9351.47, 3.914, 0, 0, 0.),\ (3489660929, 1580601599836000000, 1580601599963461000, 9397.78, 0.1 , 0, 0, 0.),\ (3489660929, 1580601599848000000, 1580601599973647000, 9348.14, 3.98 , 0, 0, 0.)\], dtype=\[('ev', '= len(out): continue \# All of our possible quotes within the order arrival depth, \# excluding those at the same price, are considered executed. out\[:tick\] += 1 max\_tick \= max(max\_tick, tick) return out\[:max\_tick\] @njit(cache\=True) def compute\_coeff(xi, gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= 1 / (xi \* delta) \* np.log(1 + xi \* delta \* inv\_k) c2 \= np.sqrt(np.divide(gamma, 2 \* A \* delta \* k) \* ((1 + xi \* delta \* inv\_k) \*\* (k / (xi \* delta) + 1))) return c1, c2 @njit(cache\=True) def linear\_regression(x, y): sx \= np.sum(x) sy \= np.sum(y) sx2 \= np.sum(x \*\* 2) sxy \= np.sum(x \* y) w \= len(x) slope \= np.divide(w \* sxy \- sx \* sy, w \* sx2 \- sx\*\*2) intercept \= np.divide(sy \- slope \* sx, w) return slope, intercept @njit def gridtrading\_glft\_mm(hbt, recorder, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan gamma \= 0.05 delta \= 1 adj1 \= 1 \# adj2 is determined according to the order quantity. grid\_num \= 20 max\_position \= grid\_num \* order\_qty adj2 \= 1 / max\_position \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff(gamma, gamma, delta, A, k) half\_spread\_tick \= (c1 + delta / 2 \* c2 \* volatility) \* adj1 skew \= c2 \* volatility \* adj2 reservation\_price\_tick \= mid\_price\_tick \- skew \* position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) The order quantity is determined to be equivalent to a notional value of $100. \[2\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/feed\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_{}.npz'.format(asset\_name)) By utilizing multiprocessing, backtesting of multiple assets can be conducted simultaneously. \[3\]: %%capture import json from multiprocessing import Pool with open('assets.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[4\]: import polars as pl from hftbacktest.stats import LinearAssetRecord equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity You can see the equity curve of individual assets and notice how combining multiple assets can lead to a smoother equity curve, thereby enhancing risk-adjusted returns. \[5\]: from matplotlib import pyplot as plt for i, asset\_name in enumerate(assets.keys()): plt.figure(i, figsize\=(10, 3)) plt.plot(equity\_values\[asset\_name\]\['timestamp'\], equity\_values\[asset\_name\]\['equity'\]) plt.grid() plt.title(asset\_name) plt.ylabel('Equity ($)') ![../_images/tutorials_Making_Multiple_Markets_8_0.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_0.png) ![../_images/tutorials_Making_Multiple_Markets_8_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_1.png) ![../_images/tutorials_Making_Multiple_Markets_8_2.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_2.png) ![../_images/tutorials_Making_Multiple_Markets_8_3.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_3.png) ![../_images/tutorials_Making_Multiple_Markets_8_4.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_4.png) ![../_images/tutorials_Making_Multiple_Markets_8_5.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_5.png) ![../_images/tutorials_Making_Multiple_Markets_8_6.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_6.png) ![../_images/tutorials_Making_Multiple_Markets_8_7.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_7.png) ![../_images/tutorials_Making_Multiple_Markets_8_8.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_8.png) ![../_images/tutorials_Making_Multiple_Markets_8_9.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_9.png) ![../_images/tutorials_Making_Multiple_Markets_8_10.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_10.png) ![../_images/tutorials_Making_Multiple_Markets_8_11.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_11.png) ![../_images/tutorials_Making_Multiple_Markets_8_12.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_12.png) ![../_images/tutorials_Making_Multiple_Markets_8_13.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_13.png) ![../_images/tutorials_Making_Multiple_Markets_8_14.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_8_14.png) This presents an equity curve based on the number of assets, which are altcoins excluding BTC and ETH. \[6\]: from matplotlib import pyplot as plt fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[6\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_10_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_10_1.png) Impact of Order Latency[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Making%20Multiple%20Markets.html#Impact-of-Order-Latency "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- When applying amplified feed latency, you can observe a decrease in performance due to the effects of latency. \[7\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/amp\_feed\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_lat1\_{}.npz'.format(asset\_name)) \[8\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[9\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_lat1\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[9\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_14_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_14_1.png) When actual historical order latency is applied, the performance may deteriorate further compared to when amplified feed latency is used. \[10\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gridtrading\_glft\_mm(hbt, recorder.recorder, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_glft\_mm\_lat2\_{}.npz'.format(asset\_name)) \[11\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[12\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_glft\_mm\_lat2\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 2\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[12\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_18_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_18_1.png) Therefore, understanding your order latency is crucial to achieving more precise backtest results. This understanding underscores the importance of latency reduction for market makers or high-frequency traders. This is why crypto exchanges not only offer maker rebates but also provide low-latency infrastructure to eligible market makers. Simpler model[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Making%20Multiple%20Markets.html#Simpler-model "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------- So far, we only cover \\(\\xi>0\\) case, but \\(\\xi=0\\) case would be more simple and appropriate in practice especially in cryptocurrencies. Revisit the equations (4.6) and (4.7) in [Optimal market making](https://arxiv.org/abs/1605.01862) and explore how they can be applied to real-world scenarios. The optimal bid quote depth, \\(\\delta^{b\*}\_{approx}\\), and ask quote depth, \\(\\delta^{a\*}\_{approx}\\), are derived from the fair price as follows in the case of \\(\\xi=0\\): \\begin{align} \\delta^{b\*}\_{approx}(q) = {1 \\over k} + {{2q + \\Delta} \\over 2}\\sqrt{{{\\gamma \\sigma^2 e} \\over {2A\\Delta k}}} \\label{eq4.6}\\tag{4.6} \\\\ \\delta^{a\*}\_{approx}(q) = {1 \\over k} - {{2q - \\Delta} \\over 2}\\sqrt{{{\\gamma \\sigma^2 e} \\over {2A\\Delta k}}} \\label{eq4.7}\\tag{4.7} \\end{align} Let’s introduce \\(c\_1\\) and \\(c\_2\\) and define them by extracting the volatility 𝜎 from the square root as same as before: \\begin{align} c\_1 = {1 \\over k} \\\\ c\_2 = \\sqrt{{{\\gamma e} \\over {2A\\Delta k}}} \\end{align} Now we can rewrite equations (4.6) and (4.7) as follows: \\begin{align} \\delta^{b\*}\_{approx}(q) = c\_1 + {\\Delta \\over 2} \\sigma c\_2 + q \\sigma c\_2 \\\\ \\delta^{a\*}\_{approx}(q) = c\_1 + {\\Delta \\over 2} \\sigma c\_2 - q \\sigma c\_2 \\end{align} It’s more concise and only need to adjust \\(\\gamma\\) and its effect is more straightforward. \[13\]: @njit(cache\=True) def compute\_coeff\_simplified(gamma, delta, A, k): inv\_k \= np.divide(1, k) c1 \= inv\_k c2 \= np.sqrt(np.divide(gamma \* np.exp(1), 2 \* A \* delta \* k)) return c1, c2 @njit def gridtrading\_glft\_mm(hbt, recorder, gamma, order\_qty): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size arrival\_depth \= np.full(30\_000\_000, np.nan, np.float64) mid\_price\_chg \= np.full(30\_000\_000, np.nan, np.float64) t \= 0 prev\_mid\_price\_tick \= np.nan mid\_price\_tick \= np.nan tmp \= np.zeros(500, np.float64) ticks \= np.arange(len(tmp)) + 0.5 A \= np.nan k \= np.nan volatility \= np.nan delta \= 1 grid\_num \= 20 max\_position \= 50 \* order\_qty \# Checks every 100 milliseconds. while hbt.elapse(100\_000\_000) \== 0: #-------------------------------------------------------- \# Records market order's arrival depth from the mid-price. if not np.isnan(mid\_price\_tick): depth \= \-np.inf for last\_trade in hbt.last\_trades(asset\_no): trade\_price\_tick \= last\_trade.px / tick\_size if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: depth \= max(trade\_price\_tick \- mid\_price\_tick, depth) else: depth \= max(mid\_price\_tick \- trade\_price\_tick, depth) arrival\_depth\[t\] \= depth hbt.clear\_last\_trades(asset\_no) hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) best\_bid\_tick \= depth.best\_bid\_tick best\_ask\_tick \= depth.best\_ask\_tick prev\_mid\_price\_tick \= mid\_price\_tick mid\_price\_tick \= (best\_bid\_tick + best\_ask\_tick) / 2.0 \# Records the mid-price change for volatility calculation. mid\_price\_chg\[t\] \= mid\_price\_tick \- prev\_mid\_price\_tick #-------------------------------------------------------- \# Calibrates A, k and calculates the market volatility. \# Updates A, k, and the volatility every 5-sec. if t % 50 \== 0: \# Window size is 10-minute. if t \>= 6\_000 \- 1: \# Calibrates A, k tmp\[:\] \= 0 lambda\_ \= measure\_trading\_intensity(arrival\_depth\[t + 1 \- 6\_000:t + 1\], tmp) if len(lambda\_) \> 2: lambda\_ \= lambda\_\[:70\] / 600 x \= ticks\[:len(lambda\_)\] y \= np.log(lambda\_) k\_, logA \= linear\_regression(x, y) A \= np.exp(logA) k \= \-k\_ \# Updates the volatility. volatility \= np.nanstd(mid\_price\_chg\[t + 1 \- 6\_000:t + 1\]) \* np.sqrt(10) #-------------------------------------------------------- \# Computes bid price and ask price. c1, c2 \= compute\_coeff\_simplified(gamma, delta, A, k) half\_spread\_tick \= c1 + delta / 2 \* c2 \* volatility skew \= c2 \* volatility normalized\_position \= position / order\_qty reservation\_price\_tick \= mid\_price\_tick \- skew \* normalized\_position bid\_price\_tick \= min(np.round(reservation\_price\_tick \- half\_spread\_tick), best\_bid\_tick) ask\_price\_tick \= max(np.round(reservation\_price\_tick + half\_spread\_tick), best\_ask\_tick) bid\_price \= bid\_price\_tick \* tick\_size ask\_price \= ask\_price\_tick \* tick\_size grid\_interval \= max(np.round(half\_spread\_tick) \* tick\_size, tick\_size) bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) #-------------------------------------------------------- \# Records variables and stats for analysis. t += 1 if t \>= len(arrival\_depth) or t \>= len(mid\_price\_chg): raise Exception \# Records the current state for stat calculation. recorder.record(hbt) \[14\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm1\_{}.npz'.format(asset\_name)) \[15\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[16\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm1\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 5\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[16\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_24_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_24_1.png) \[17\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230630\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230701, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230701, 20230732)\]) .initial\_snapshot('data/{}\_20230630\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.001 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm2\_{}.npz'.format(asset\_name)) \[18\]: %%capture with Pool(16) as p: print(p.map(backtest, list(assets.items()))) You can observe a more straight line in the equity curve with higher \\(\\gamma\\), which induces greater skew. However, it also experiences more severe drawdowns in fast-moving markets. Additionally, because of the higher skew, profits are diminished as there’s a greater tendency to close the position. \[19\]: equity\_values \= {} for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm2\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) equity\_values\[asset\_name\] \= equity fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] net\_equity \= None for i, equity in enumerate(list(equity\_values.values())): asset\_number \= i + 1 if net\_equity is None: net\_equity \= equity\['equity'\].clone() else: net\_equity += equity\['equity'\].clone() if asset\_number % 5 \== 0: \# 2\_000 is capital for each trading asset. net\_equity\_df \= pl.DataFrame({ 'cum\_ret': (net\_equity / asset\_number) / 5\_000 \* 100, 'timestamp': equity\['timestamp'\] }) net\_equity\_rs\_df \= net\_equity\_df.group\_by\_dynamic( index\_column\='timestamp', every\='1d' ).agg(\[\ pl.col('cum\_ret').last()\ \]) pnl \= net\_equity\_rs\_df\['cum\_ret'\].diff() sr \= pnl.mean() / pnl.std() ann\_sr \= sr \* np.sqrt(365) plt.plot(net\_equity\_df\['timestamp'\], net\_equity\_df\['cum\_ret'\]) legend.append('{} assets, SR={:.2f} (Daily SR={:.2f})'.format(asset\_number, ann\_sr, sr)) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[19\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_28_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_28_1.png) A Case for More Assets[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Making%20Multiple%20Markets.html#A-Case-for-More-Assets "Link to this heading") -------------------------------------------------------------------------------------------------------------------------------------------------------------------- The more assets you make a market for, the better risk-adjusted return you achieve. This effect becomes dramatically evident. \[20\]: def backtest(args): asset\_name, asset\_info \= args \# Obtains the mid-price of the assset to determine the order quantity. snapshot \= np.load('data/{}\_20230730\_eod.npz'.format(asset\_name))\['data'\] best\_bid \= max(snapshot\[snapshot\['ev'\] & BUY\_EVENT \== BUY\_EVENT\]\['px'\]) best\_ask \= min(snapshot\[snapshot\['ev'\] & SELL\_EVENT \== SELL\_EVENT\]\['px'\]) mid\_price \= (best\_bid + best\_ask) / 2.0 latency\_data \= np.concatenate( \[np.load('latency/live\_order\_latency\_{}.npz'.format(date))\['data'\] for date in range(20230731, 20230732)\] ) asset \= ( BacktestAsset() .data(\['data/{}\_{}.npz'.format(asset\_name, date) for date in range(20230731, 20230732)\]) .initial\_snapshot('data/{}\_20230730\_eod.npz'.format(asset\_name)) .linear\_asset(1.0) .intp\_order\_latency(latency\_data) .power\_prob\_queue\_model(2.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(asset\_info\['tick\_size'\]) .lot\_size(asset\_info\['lot\_size'\]) .roi\_lb(0.0) .roi\_ub(mid\_price \* 5) .last\_trades\_capacity(10000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) \# Sets the order quantity to be equivalent to a notional value of $100. order\_qty \= max(round((100 / mid\_price) / asset\_info\['lot\_size'\]), 1) \* asset\_info\['lot\_size'\] recorder \= Recorder(1, 30\_000\_000) gamma \= 0.00005 gridtrading\_glft\_mm(hbt, recorder.recorder, gamma, order\_qty) hbt.close() recorder.to\_npz('stats/gridtrading\_simple\_glft\_mm3\_{}.npz'.format(asset\_name)) \[21\]: %%capture with open('assets2.json', 'r') as f: assets \= json.load(f) with Pool(16) as p: print(p.map(backtest, list(assets.items()))) \[22\]: equity\_values \= {} sr\_values \= {} np.seterr(divide\='ignore', invalid\='ignore') for asset\_name in assets.keys(): data \= np.load('stats/gridtrading\_simple\_glft\_mm3\_{}.npz'.format(asset\_name))\['0'\] stats \= ( LinearAssetRecord(data) .resample('5m') .stats() ) equity \= stats.entire.with\_columns( (pl.col('equity\_wo\_fee') \- pl.col('fee')).alias('equity') ).select(\['timestamp', 'equity'\]) pnl \= equity\['equity'\].diff() sr \= np.divide(pnl.mean(), pnl.std()) equity\_values\[asset\_name\] \= equity sr\_values\[asset\_name\] \= sr sr\_m \= np.nanmean(list(sr\_values.values())) sr\_s \= np.nanstd(list(sr\_values.values())) fig \= plt.figure() fig.set\_size\_inches(10, 3) legend \= \[\] asset\_number \= 0 net\_equity \= None for i, (equity, sr) in enumerate(zip(equity\_values.values(), sr\_values.values())): \# There are some assets that aren't working within this scheme. \# This might be because the order arrivals don't follow a Poisson distribution that this model assumes. \# As a result, it filters out assets whose SR falls outside -0.5 sigma. if (sr \- sr\_m) / sr\_s \> \-0.5: asset\_number += 1 if net\_equity is None: net\_equity \= equity.clone() else: net\_equity \= net\_equity.select( 'timestamp', (pl.col('equity') + equity\['equity'\]).alias('equity') ) if asset\_number % 10 \== 0: \# 5\_000 is capital for each trading asset. net\_equity\_ \= net\_equity\['equity'\] / asset\_number / 5\_000 pnl \= net\_equity\_.diff() \# Since the P&L is resampled at a 5-minute interval sr \= pnl.mean() / pnl.std() \* np.sqrt(24 \* 60 / 5) legend.append('{} assets,Daily SR={:.2f}'.format(asset\_number, sr)) plt.plot(net\_equity\['timestamp'\], net\_equity\_ \* 100) plt.legend( legend, loc\='upper center', bbox\_to\_anchor\=(0.5, \-0.15), fancybox\=True, shadow\=True, ncol\=3 ) plt.grid() plt.ylabel('Cumulative Returns (%)') \[22\]: Text(0, 0.5, 'Cumulative Returns (%)') ![../_images/tutorials_Making_Multiple_Markets_32_1.png](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_images/tutorials_Making_Multiple_Markets_32_1.png) --- # Integrating Custom Data — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Integrating Custom Data * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Integrating%20Custom%20Data.ipynb.txt) * * * Integrating Custom Data[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Integrating%20Custom%20Data.html#Integrating-Custom-Data "Link to this heading") ====================================================================================================================================================================== By combining your custom data with the feed data (order book and trades), you can enhance your strategy while harnessing the full potential of hftbacktest. Accessing Spot Price[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Integrating%20Custom%20Data.html#Accessing-Spot-Price "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------- In this example, we’ll combine the spot BTCUSDT mid-price with the USDM-Futures BTCUSDT feed data. This will enable you to estimate the fair value price, taking the underlying price into consideration. The spot data is used only in the local-side, and thus, should come with a local timestamp. Following this, in your backtesting logic, your task is to identify the most recent data that predates the current timestamp. The raw spot feed is processed to create spot data, which includes both a local timestamp and the spot mid price. \[1\]: import numpy as np import gzip import json spot \= np.full((100\_000, 2), np.nan, np.float64) i \= 0 with gzip.open('spot/btcusdt\_20240809.gz', 'r') as f: while True: line \= f.readline() if line is None or line \== b'': break line \= line.decode().strip() local\_timestamp \= int(line\[:19\]) obj \= json.loads(line\[20:\]) if obj\['stream'\] \== 'btcusdt@bookTicker': data \= obj\['data'\] mid \= (float(data\['b'\]) + float(data\['a'\])) / 2.0 spot\[i\] \= \[local\_timestamp, mid\] i += 1 spot \= spot\[:i\] It displays the basis and spot mid price as it identifies the latest Point-in-Time data that falls before the current timestamp. \[2\]: from numba import njit from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest out\_dtype \= np.dtype(\[('timestamp', 'i8'), ('mid\_price', 'f8'), ('spot\_mid\_price', 'f8')\]) @njit def print\_basis(hbt, spot): out \= np.empty(1\_000\_000, out\_dtype) t \= 0 spot\_row \= 0 \# Checks every 60-sec (in nanoseconds) while hbt.elapse(1\_000\_000\_000) \== 0: \# Finds the latest spot mid value. while spot\_row < len(spot) and spot\[spot\_row, 0\] <= hbt.current\_timestamp: spot\_row += 1 spot\_mid\_price \= spot\[spot\_row \- 1, 1\] if spot\_row \> 0 else np.nan depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 basis \= mid\_price \- spot\_mid\_price if t % 10 \== 0: print( 'current\_timestamp:', hbt.current\_timestamp, 'futures\_mid:', round(mid\_price, 2), ', spot\_mid:', round(spot\_mid\_price, 2), ', basis:', round(basis, 2) ) out\[t\].timestamp \= hbt.current\_timestamp out\[t\].mid\_price \= mid\_price out\[t\].spot\_mid\_price \= spot\_mid\_price t += 1 return out\[:t\] asset \= ( BacktestAsset() .data(\['usdm/btcusdt\_20240809.npz'\]) .initial\_snapshot('usdm/btcusdt\_20240808\_eod.npz') .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) out \= print\_basis(hbt, spot) \_ \= hbt.close() current\_timestamp: 1723161602500000000 futures\_mid: 61659.85 , spot\_mid: 61688.0 , basis: -28.14 current\_timestamp: 1723161612500000000 futures\_mid: 61713.95 , spot\_mid: 61727.8 , basis: -13.85 current\_timestamp: 1723161622500000000 futures\_mid: 61713.45 , spot\_mid: 61728.94 , basis: -15.5 current\_timestamp: 1723161632500000000 futures\_mid: 61666.05 , spot\_mid: 61690.08 , basis: -24.02 current\_timestamp: 1723161642500000000 futures\_mid: 61638.45 , spot\_mid: 61661.5 , basis: -23.06 current\_timestamp: 1723161652500000000 futures\_mid: 61632.05 , spot\_mid: 61663.98 , basis: -31.93 current\_timestamp: 1723161662500000000 futures\_mid: 61578.15 , spot\_mid: 61600.0 , basis: -21.85 current\_timestamp: 1723161672500000000 futures\_mid: 61524.25 , spot\_mid: 61562.0 , basis: -37.74 current\_timestamp: 1723161682500000000 futures\_mid: 61552.45 , spot\_mid: 61570.0 , basis: -17.54 current\_timestamp: 1723161692500000000 futures\_mid: 61593.05 , spot\_mid: 61606.0 , basis: -12.96 current\_timestamp: 1723161702500000000 futures\_mid: 61587.45 , spot\_mid: 61608.0 , basis: -20.54 current\_timestamp: 1723161712500000000 futures\_mid: 61561.15 , spot\_mid: 61589.88 , basis: -28.73 current\_timestamp: 1723161722500000000 futures\_mid: 61589.95 , spot\_mid: 61614.08 , basis: -24.14 current\_timestamp: 1723161732500000000 futures\_mid: 61608.95 , spot\_mid: 61632.13 , basis: -23.18 current\_timestamp: 1723161742500000000 futures\_mid: 61653.45 , spot\_mid: 61681.74 , basis: -28.29 current\_timestamp: 1723161752500000000 futures\_mid: 61673.45 , spot\_mid: 61700.0 , basis: -26.54 current\_timestamp: 1723161762500000000 futures\_mid: 61663.95 , spot\_mid: 61683.84 , basis: -19.89 current\_timestamp: 1723161772500000000 futures\_mid: 61640.85 , spot\_mid: 61664.0 , basis: -23.15 current\_timestamp: 1723161782500000000 futures\_mid: 61634.15 , spot\_mid: 61654.0 , basis: -19.85 current\_timestamp: 1723161792500000000 futures\_mid: 61618.05 , spot\_mid: 61666.0 , basis: -47.94 current\_timestamp: 1723161802500000000 futures\_mid: 61626.65 , spot\_mid: 61648.34 , basis: -21.69 current\_timestamp: 1723161812500000000 futures\_mid: 61586.25 , spot\_mid: 61612.0 , basis: -25.74 current\_timestamp: 1723161822500000000 futures\_mid: 61624.65 , spot\_mid: 61649.98 , basis: -25.33 current\_timestamp: 1723161832500000000 futures\_mid: 61611.55 , spot\_mid: 61644.0 , basis: -32.46 current\_timestamp: 1723161842500000000 futures\_mid: 61633.95 , spot\_mid: 61658.4 , basis: -24.46 current\_timestamp: 1723161852500000000 futures\_mid: 61635.95 , spot\_mid: 61656.02 , basis: -20.07 current\_timestamp: 1723161862500000000 futures\_mid: 61671.45 , spot\_mid: 61689.92 , basis: -18.47 current\_timestamp: 1723161872500000000 futures\_mid: 61651.55 , spot\_mid: 61664.0 , basis: -12.46 current\_timestamp: 1723161882500000000 futures\_mid: 61614.15 , spot\_mid: 61640.0 , basis: -25.84 current\_timestamp: 1723161892500000000 futures\_mid: 61605.95 , spot\_mid: 61622.12 , basis: -16.18 current\_timestamp: 1723161902500000000 futures\_mid: 61583.95 , spot\_mid: 61607.98 , basis: -24.04 \[3\]: import polars as pl import holoviews as hv df \= pl.DataFrame(out).with\_columns( pl.from\_epoch('timestamp', time\_unit\='ns').alias('timestamp') ) hv.extension('bokeh') df.plot(x\='timestamp') ![]() ![]() \[3\]: Although this is a short-period sample, you can observe that the basis is mean-reverting. There may be statistical arbitrage opportunities, particularly if you are eligible for rebates or zero fees. \[4\]: ((df\['mid\_price'\] \- df\['spot\_mid\_price'\]) / df\['mid\_price'\] \* 10000).alias('basis bp').plot(x\='timestamp') \[4\]: --- # Working with Market Depth and Trades — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.1.0/index.html) * Working with Market Depth and Trades * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.1.0/_sources/tutorials/Working%20with%20Market%20Depth%20and%20Trades.ipynb.txt) * * * Working with Market Depth and Trades[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Working-with-Market-Depth-and-Trades "Link to this heading") =================================================================================================================================================================================================================== Display 3-depth[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-3-depth "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[1\]: from numba import njit @njit def print\_3depth(hbt): while hbt.elapse(60\_000\_000\_000) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = rice / tick\_size i \= 0 for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): qty \= depth.ask\_qty\_at\_tick(price\_tick) if qty \> 0: print( 'ask: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break i \= 0 for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick \- 100, 0), \-1): qty \= depth.bid\_qty\_at\_tick(price\_tick) if qty \> 0: print( 'bid: ', qty, '@', np.round(price\_tick \* depth.tick\_size, 1) ) i += 1 if i \== 3: break return True \[2\]: import numpy as np btcusdt\_20240809 \= np.load('usdm/btcusdt\_20240809.npz')\['data'\] btcusdt\_20240808\_eod \= np.load('usdm/btcusdt\_20240808\_eod.npz')\['data'\] \[3\]: from hftbacktest import BacktestAsset, HashMapMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) ) hbt \= HashMapMarketDepthBacktest(\[asset\]) print\_3depth(hbt) \_ \= hbt.close() current\_timestamp: 1723161661500000000 ask: 1.759 @ 61594.2 ask: 0.006 @ 61594.4 ask: 0.114 @ 61595.2 bid: 3.526 @ 61594.1 bid: 0.016 @ 61594.0 bid: 0.002 @ 61593.9 current\_timestamp: 1723161721500000000 ask: 2.575 @ 61576.6 ask: 0.004 @ 61576.7 ask: 0.455 @ 61577.0 bid: 2.558 @ 61576.5 bid: 0.002 @ 61576.0 bid: 0.515 @ 61575.5 current\_timestamp: 1723161781500000000 ask: 0.131 @ 61629.7 ask: 0.005 @ 61630.1 ask: 0.005 @ 61630.5 bid: 5.742 @ 61629.6 bid: 0.247 @ 61629.4 bid: 0.034 @ 61629.3 current\_timestamp: 1723161841500000000 ask: 0.202 @ 61621.6 ask: 0.002 @ 61622.5 ask: 0.003 @ 61622.6 bid: 3.488 @ 61621.5 bid: 0.86 @ 61620.0 bid: 0.248 @ 61619.6 current\_timestamp: 1723161901500000000 ask: 1.397 @ 61584.0 ask: 0.832 @ 61585.1 ask: 0.132 @ 61586.0 bid: 3.307 @ 61583.9 bid: 0.01 @ 61583.8 bid: 0.002 @ 61582.0 Efficient Market Depth Access[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Efficient-Market-Depth-Access "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `ROIVectorMarketDepth` provides more efficient market depth access through a vector that holds a limited price range of interest. The backtester using this feature can be created by `ROIVectorMarketDepthBacktest`. \[4\]: from numba import njit @njit def print\_3depth\_fast(hbt): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(60\_000\_000\_000) \== 0: print('current\_timestamp:', hbt.current\_timestamp) \# Gets the market depth for the first asset, in the same order as when you created the backtest. depth \= hbt.depth(0) \# a key of bid\_depth or ask\_depth is price in ticks. \# (integer) price\_tick = price / tick\_size i \= 0 \# for price\_tick in range(depth.best\_ask\_tick, depth.best\_ask\_tick + 100): \# # depth.ask\_depth returns the ask depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# # containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# # Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. \# if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick: \# continue \# t = price\_tick - roi\_lb\_tick \# qty = depth.ask\_depth\[t\] \# if qty > 0: \# print( \# 'ask: ', \# qty, \# '@', \# np.round(price\_tick \* depth.tick\_size, 1) \# ) \# i += 1 \# if i == 3: \# break \# i = 0 \# for price\_tick in range(depth.best\_bid\_tick, max(depth.best\_bid\_tick - 100, 0), -1): \# # depth.bid\_depth returns the bid depth array, whose length is (roi\_ub\_tick + 1 - roi\_lb\_tick), \# # containing the quantities ranging from roi\_lb\_tick to roi\_ub\_tick. \# # Checks that the price\_tick is in that range and adjust the index by subtracting roi\_lb\_tick. \# if price\_tick < roi\_lb\_tick or price\_tick > roi\_ub\_tick: \# continue \# t = price\_tick - roi\_lb\_tick \# qty = depth.bid\_depth\[t\] \# if qty > 0: \# print( \# 'bid: ', \# qty, \# '@', \# np.round(price\_tick \* depth.tick\_size, 1) \# ) \# i += 1 \# if i == 3: \# break return True \[5\]: from hftbacktest import ROIVectorMarketDepthBacktest asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# Sets the lower bound price for the range of interest in the market depth. .roi\_lb(30000) \# Sets the upper bound price for the range of interest in the market depth. .roi\_ub(90000) ) \[6\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_3depth\_fast(hbt) #\_ = hbt.close() current\_timestamp: 1723161661500000000 current\_timestamp: 1723161721500000000 current\_timestamp: 1723161781500000000 current\_timestamp: 1723161841500000000 current\_timestamp: 1723161901500000000 \[6\]: True Order Book Imbalance[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Order-Book-Imbalance "Link to this heading") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[7\]: @njit def orderbookimbalance(hbt, out): roi\_lb\_tick \= int(round(30000 / 0.1)) roi\_ub\_tick \= int(round(90000 / 0.1)) while hbt.elapse(10 \* 1e9) \== 0: depth \= hbt.depth(0) mid\_price \= (depth.best\_bid + depth.best\_ask) / 2.0 sum\_ask\_qty\_50bp \= 0.0 sum\_ask\_qty \= 0.0 for price\_tick in range(depth.best\_ask\_tick, roi\_ub\_tick + 1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick ask\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (ask\_price \- mid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_ask\_qty += depth.ask\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_ask\_qty\_50bp \= sum\_ask\_qty sum\_bid\_qty\_50bp \= 0.0 sum\_bid\_qty \= 0.0 for price\_tick in range(depth.best\_bid\_tick, roi\_lb\_tick \- 1, \-1): if price\_tick < roi\_lb\_tick or price\_tick \> roi\_ub\_tick: continue t \= price\_tick \- roi\_lb\_tick bid\_price \= price\_tick \* depth.tick\_size depth\_from\_mid \= (mid\_price \- bid\_price) / mid\_price if depth\_from\_mid \> 0.01: break sum\_bid\_qty += depth.bid\_depth\[t\] if depth\_from\_mid <= 0.005: sum\_bid\_qty\_50bp \= sum\_bid\_qty imbalance\_50bp \= sum\_bid\_qty\_50bp \- sum\_ask\_qty\_50bp imbalance\_1pct \= sum\_bid\_qty \- sum\_ask\_qty imbalance\_tob \= depth.bid\_depth\[depth.best\_bid\_tick \- roi\_lb\_tick\] \- depth.ask\_depth\[depth.best\_ask\_tick \- roi\_lb\_tick\] out.append((hbt.current\_timestamp, imbalance\_tob, imbalance\_50bp, imbalance\_1pct)) return True \[8\]: from numba.typed import List from numba.types import Tuple, float64 hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) orderbookimbalance(hbt, out) \_ \= hbt.close() \[9\]: import polars as pl df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', 'TOB Imbalance', '0.5% Imbalance', '1% Imbalance'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[9\]: shape: (30, 4) | Local Timestamp | TOB Imbalance | 0.5% Imbalance | 1% Imbalance | | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 2.729 | \-1748.101 | \-3908.736 | | 2024-08-09 00:00:21.500 | 4.623 | \-1749.435 | \-3512.845 | | 2024-08-09 00:00:31.500 | \-6.465 | \-1259.897 | \-3357.755 | | 2024-08-09 00:00:41.500 | \-7.922 | \-1174.185 | \-3471.955 | | 2024-08-09 00:00:51.500 | \-2.484 | \-1147.597 | \-3461.48 | | … | … | … | … | | 2024-08-09 00:04:21.500 | 3.828 | \-1186.236 | \-3551.78 | | 2024-08-09 00:04:31.500 | \-1.35 | \-1332.379 | \-3517.854 | | 2024-08-09 00:04:41.500 | \-3.754 | \-1166.521 | \-2693.672 | | 2024-08-09 00:04:51.500 | \-2.525 | \-1188.56 | \-2716.914 | | 2024-08-09 00:05:01.500 | 1.91 | \-594.991 | \-2138.82 | \[10\]: import holoviews as hv hv.extension('bokeh') df.plot(x\='Local Timestamp') ![]() ![]() \[10\]: Display last trades between the step[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Display-last-trades-between-the-step "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[11\]: from hftbacktest import BUY\_EVENT @njit def print\_trades(hbt): while hbt.elapse(60 \* 1e9) \== 0: print('-------------------------------------------------------------------------------') print('current\_timestamp:', hbt.current\_timestamp) \# Gets the last trades occurring in the market, not the trades of our orders. last\_trades \= hbt.last\_trades(0) num \= 0 for last\_trade in last\_trades: if num \> 10: print('...') break print( 'exch\_timestamp:', last\_trade.exch\_ts, 'buy' if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT else 'sell', last\_trade.qty, '@', last\_trade.px ) num += 1 \# To prevent accumulating all last trades, which may cause a slowdown, \# clear\_last\_trades needs to be called. \# After this, accessing \`last\_trades\` will cause a crash. hbt.clear\_last\_trades(0) return True \[12\]: asset \= ( BacktestAsset() .data(btcusdt\_20240809) .initial\_snapshot(btcusdt\_20240808\_eod) .linear\_asset(1.0) .constant\_latency(10\_000\_000, 10\_000\_000) .risk\_adverse\_queue\_model() .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(0.0002, 0.0007) .tick\_size(0.1) .lot\_size(0.001) \# To retrieve the last trades, \`last\_trades\_capacity\` should be set. .last\_trades\_capacity(1000) .roi\_lb(30000) .roi\_ub(90000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) print\_trades(hbt) \_ \= hbt.close() \------------------------------------------------------------------------------- current\_timestamp: 1723161661500000000 exch\_timestamp: 1723161602372000000 buy 0.489 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.198 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.006 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.011 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.238 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.007 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.005 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.003 @ 61659.8 exch\_timestamp: 1723161602372000000 buy 0.002 @ 61659.8 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161721500000000 exch\_timestamp: 1723161661697000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661724000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661751000000 buy 0.135 @ 61594.2 exch\_timestamp: 1723161661806000000 sell 1.328 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.002 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.006 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.32 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 0.032 @ 61594.1 exch\_timestamp: 1723161661806000000 sell 1.208 @ 61594.1 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161781500000000 exch\_timestamp: 1723161721541000000 sell 0.002 @ 61576.5 exch\_timestamp: 1723161721574000000 buy 0.012 @ 61576.6 exch\_timestamp: 1723161721578000000 sell 0.003 @ 61576.5 exch\_timestamp: 1723161721583000000 buy 0.275 @ 61576.6 exch\_timestamp: 1723161721583000000 buy 0.469 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.095 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.102 @ 61576.6 exch\_timestamp: 1723161721585000000 buy 0.197 @ 61576.6 exch\_timestamp: 1723161721586000000 buy 0.13 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.425 @ 61576.6 exch\_timestamp: 1723161721587000000 buy 0.324 @ 61576.6 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161841500000000 exch\_timestamp: 1723161781628000000 sell 0.026 @ 61629.6 exch\_timestamp: 1723161781727000000 buy 0.011 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.05 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.006 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.007 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.075 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.065 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.247 @ 61629.7 exch\_timestamp: 1723161781727000000 buy 0.002 @ 61629.7 ... ------------------------------------------------------------------------------- current\_timestamp: 1723161901500000000 exch\_timestamp: 1723161841561000000 buy 0.01 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.006 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.002 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.022 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.097 @ 61621.6 exch\_timestamp: 1723161841561000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.024 @ 61621.6 exch\_timestamp: 1723161841564000000 buy 0.014 @ 61621.6 exch\_timestamp: 1723161841565000000 buy 0.003 @ 61621.6 exch\_timestamp: 1723161841613000000 buy 0.002 @ 61622.5 exch\_timestamp: 1723161841613000000 buy 0.003 @ 61622.6 ... Rolling Volume-Weighted Average Price[](https://hftbacktest.readthedocs.io/en/py-v2.1.0/tutorials/Working%20with%20Market%20Depth%20and%20Trades.html#Rolling-Volume-Weighted-Average-Price "Link to this heading") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \[13\]: @njit def rolling\_vwap(hbt, out): buy\_amount\_bin \= np.zeros(100\_000, np.float64) buy\_qty\_bin \= np.zeros(100\_000, np.float64) sell\_amount\_bin \= np.zeros(100\_000, np.float64) sell\_qty\_bin \= np.zeros(100\_000, np.float64) idx \= 0 last\_trade\_price \= np.nan while hbt.elapse(10 \* 1e9) \== 0: last\_trades \= hbt.last\_trades(0) for last\_trade in last\_trades: if (last\_trade.ev & BUY\_EVENT) \== BUY\_EVENT: buy\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty buy\_qty\_bin\[idx\] += last\_trade.qty else: sell\_amount\_bin\[idx\] += last\_trade.px \* last\_trade.qty sell\_qty\_bin\[idx\] += last\_trade.qty hbt.clear\_last\_trades(0) idx += 1 if idx \>= 1: vwap10sec \= np.divide( buy\_amount\_bin\[idx \- 1\] + sell\_amount\_bin\[idx \- 1\], buy\_qty\_bin\[idx \- 1\] + sell\_qty\_bin\[idx \- 1\] ) else: vwap10sec \= np.nan if idx \>= 6: vwap1m \= np.divide( np.sum(buy\_amount\_bin\[idx \- 6:idx\]) + np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\]) + np.sum(sell\_qty\_bin\[idx \- 6:idx\]) ) buy\_vwap1m \= np.divide(np.sum(buy\_amount\_bin\[idx \- 6:idx\]), np.sum(buy\_qty\_bin\[idx \- 6:idx\])) sell\_vwap1m \= np.divide(np.sum(sell\_amount\_bin\[idx \- 6:idx\]), np.sum(sell\_qty\_bin\[idx \- 6:idx\])) else: vwap1m \= np.nan buy\_vwap1m \= np.nan sell\_vwap1m \= np.nan out.append((hbt.current\_timestamp, vwap10sec, vwap1m, buy\_vwap1m, sell\_vwap1m)) return True \[14\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) tup\_ty \= Tuple((float64, float64, float64, float64, float64)) out \= List.empty\_list(tup\_ty, allocated\=100\_000) rolling\_vwap(hbt, out) \_ \= hbt.close() \[15\]: df \= pl.DataFrame(out).transpose() df.columns \= \['Local Timestamp', '10-sec VWAP', '1-min VWAP', '1-min Buy VWAP', '1-min Sell VWAP'\] df \= df.with\_columns( pl.from\_epoch('Local Timestamp', time\_unit\='ns') ) df \[15\]: shape: (30, 5) | Local Timestamp | 10-sec VWAP | 1-min VWAP | 1-min Buy VWAP | 1-min Sell VWAP | | --- | --- | --- | --- | --- | | datetime\[ns\] | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | | 2024-08-09 00:00:11.500 | 61687.182976 | NaN | NaN | NaN | | 2024-08-09 00:00:21.500 | 61709.337576 | NaN | NaN | NaN | | 2024-08-09 00:00:31.500 | 61697.538054 | NaN | NaN | NaN | | 2024-08-09 00:00:41.500 | 61663.958879 | NaN | NaN | NaN | | 2024-08-09 00:00:51.500 | 61637.340621 | NaN | NaN | NaN | | … | … | … | … | … | | 2024-08-09 00:04:21.500 | 61643.009847 | 61624.459011 | 61626.495542 | 61622.549429 | | 2024-08-09 00:04:31.500 | 61670.795685 | 61635.877251 | 61638.362314 | 61632.48854 | | 2024-08-09 00:04:41.500 | 61643.108582 | 61641.846489 | 61648.672337 | 61636.032054 | | 2024-08-09 00:04:51.500 | 61614.723569 | 61640.490841 | 61647.769844 | 61634.372128 | | 2024-08-09 00:05:01.500 | 61584.697467 | 61637.334102 | 61642.209551 | 61632.12064 | \[16\]: df.plot(x\='Local Timestamp') \[16\]: --- # Constants — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Constants * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/reference/constants.rst.txt) * * * Constants[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#constants "Link to this heading") ======================================================================================================================== EXCH\_EVENT _\= 2147483648_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.EXCH_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the exchange processor at the exchange timestamp. LOCAL\_EVENT _\= 1073741824_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.LOCAL_EVENT "Link to this definition") Indicates that it is a valid event to be handled by the local processor at the local timestamp. BUY\_EVENT _\= 536870912_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.BUY_EVENT "Link to this definition") Indicates a buy, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means a bid-side event, while when combined with a trade event, it means that the trade initiator is a buyer. SELL\_EVENT _\= 268435456_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.SELL_EVENT "Link to this definition") Indicates a sell, with specific meaning that can vary depending on the situation. For example, when combined with a depth event, it means an ask-side event, while when combined with a trade event, it means that the trade initiator is a seller. MARKET _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.MARKET "Link to this definition") MARKET LIMIT _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.LIMIT "Link to this definition") LIMIT BUY _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.BUY "Link to this definition") In the market depth event, this indicates the bid side; in the market trade event, it indicates that the trade initiator is a buyer. SELL _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.SELL "Link to this definition") In the market depth event, this indicates the ask side; in the market trade event, it indicates that the trade initiator is a seller. NONE _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.NONE "Link to this definition") NONE NEW _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.NEW "Link to this definition") NEW EXPIRED _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.EXPIRED "Link to this definition") EXPIRED FILLED _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.FILLED "Link to this definition") FILLED PARTIALLY\_FILLED _\= 5_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.PARTIALLY_FILLED "Link to this definition") PARTIALLY\_FILLED CANCELED _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.CANCELED "Link to this definition") CANCELED REJECTED _\= 6_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.REJECTED "Link to this definition") REJECTED GTC _\= 0_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.GTC "Link to this definition") Good ‘till cancel GTX _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.GTX "Link to this definition") Post only FOK _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.FOK "Link to this definition") Fill or kill IOC _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.order.IOC "Link to this definition") Immediate or cancel ALL\_ASSETS _\= \-1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.ALL_ASSETS "Link to this definition") Indicates all assets. DEPTH\_EVENT _\= 1_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_EVENT "Link to this definition") Indicates that the market depth is changed. TRADE\_EVENT _\= 2_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.TRADE_EVENT "Link to this definition") Indicates that a trade occurs in the market. DEPTH\_CLEAR\_EVENT _\= 3_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_CLEAR_EVENT "Link to this definition") Indicates that the market depth is cleared. DEPTH\_SNAPSHOT\_EVENT _\= 4_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.DEPTH_SNAPSHOT_EVENT "Link to this definition") Indicates that the market depth snapshot is received. UNTIL\_END\_OF\_DATA _\= 9223372036854775807_[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/reference/constants.html#hftbacktest.types.UNTIL_END_OF_DATA "Link to this definition") Indicates that one should continue until the end of the data. --- # Queue-Based Market Making in Large Tick Size Assets — hftbacktest * [](https://hftbacktest.readthedocs.io/en/py-v2.3.0/index.html) * Queue-Based Market Making in Large Tick Size Assets * [View page source](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_sources/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.ipynb.txt) * * * Queue-Based Market Making in Large Tick Size Assets[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Queue-Based-Market-Making-in-Large-Tick-Size-Assets "Link to this heading") ==================================================================================================================================================================================================================================================================== Overview[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Overview "Link to this heading") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The significance of queue position is well-known in microstructure trading, particularly in assets with large tick sizes. This is because larger tick assets typically more constrained price movements. The impact of tick size is discussed in detail in [“Large tick assets: implicit spread and optimal tick size”](https://arxiv.org/pdf/1207.6325) . ![CRVUSDT_chart](https://github.com/nkaz001/hftbacktest/blob/master/docs/images/CRVUSDT_chart.png) **Note:** This example is for educational purposes only and demonstrates effective strategies for high-frequency market-making schemes. All backtests are based on a 0.005% rebate, the highest market maker rebate available on Binance Futures. See Binance Upgrades USDⓢ-Margined Futures Liquidity Provider Program for more details. Book Pressure[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Book-Pressure "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To begin, we will review the [Market Microstructure signals described in this article](https://blog.headlandstech.com/2017/08/) , which are similar to the concept of micro-price. Book imbalance is also addressed in [Market Making with Alpha - Order Book Imbalance](https://github.com/nkaz001/hftbacktest/blob/master/examples/Market%20Making%20with%20Alpha%20-%20Order%20Book%20Imbalance.ipynb) . \[1\]: import numpy as np from numba import njit, uint64, float64 from numba.typed import Dict from hftbacktest import BUY, SELL, GTX, LIMIT @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. \# If you set a lower value, the order will tend to stay to the best bid and offer, even when book pressure increases. \# You can think of it as a threshold for backing off based on book pressure. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= book\_pressure \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[2\]: from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest, Recorder asset \= ( BacktestAsset() .data(\[\ f'data/CRVUSDT\_{date}.npz' for date in range(20240701, 20240732)\ \] + \[\ f'data/CRVUSDT\_{date}.npz' for date in range(20240801, 20240832)\ \]) .linear\_asset(1.0) .intp\_order\_latency(\[\ f'latency/amp\_feed\_latency\_{date}.npz' for date in range(20240701, 20240732)\ \] + \[\ f'latency/amp\_feed\_latency\_{date}.npz' for date in range(20240801, 20240832)\ \]) .power\_prob\_queue\_model(3.0) .no\_partial\_fill\_exchange() .trading\_value\_fee\_model(\-0.00005, 0.0007) .tick\_size(0.001) .lot\_size(0.1) .roi\_lb(0.0) .roi\_ub(2.0) .last\_trades\_capacity(1000) ) hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[3\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 14s, sys: 8.57 s, total: 8min 23s Wall time: 6min 49s \[4\]: from hftbacktest.stats import LinearAssetRecord stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[4\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 16.386564 | 23.90151 | 2.848749 | 0.096359 | 106.774393 | 30.241524 | 29.563923 | 0.001519 | 2.4745 | \[5\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_5_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_5_0.png) Trade Impulse[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Trade-Impulse "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let’s examine how it changes when we incorporate the trade impulse. \[6\]: from hftbacktest import BUY\_EVENT @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 trade\_impulse\_adj \= 1.0 sum\_bbo\_qty \= np.empty(50\_000\_000, float64) i \= 0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) \# Computes the trade impulse last\_qty \= 0 if len(last\_trades) \> 0: if last\_trades\[\-1\].ev & BUY\_EVENT \== BUY\_EVENT: last\_qty \= last\_trades\[\-1\].qty else: last\_qty \= \-last\_trades\[\-1\].qty hbt.clear\_last\_trades(asset\_no) sum\_bbo\_qty\[i\] \= best\_bid\_qty + best\_ask\_qty i += 1 \# Uses the last 1-minute average BBO quantity as the denominator. trade\_impulse \= (tick\_size / 2.0) \* last\_qty / np.mean(sum\_bbo\_qty\[max(0, i \- 600):i\]) fair\_price \= book\_pressure + trade\_impulse \* trade\_impulse\_adj skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= fair\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[7\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[8\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 13s, sys: 8.03 s, total: 8min 21s Wall time: 6min 48s \[9\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[9\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 16.52588 | 24.122559 | 2.874579 | 0.096359 | 106.580844 | 30.186685 | 29.831983 | 0.001536 | 2.4745 | \[10\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_11_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_11_0.png) There is not much difference, as the last trade quantity is relatively small compared to the best bid and offer quantities. ![CRVUSDT_depth](https://github.com/nkaz001/hftbacktest/blob/master/docs/images/CRVUSDT_depth.png) The following example demonstrates a variant of trade impulse using aggregated trade quantities. \[11\]: @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 trade\_impulse\_adj \= 1.0 sum\_bbo\_qty \= np.empty(50\_000\_000, float64) i \= 0 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] \# Market microstructure signals in https://blog.headlandstech.com/2017/08/ book\_pressure \= (best\_bid \* best\_ask\_qty + best\_ask \* best\_bid\_qty) / (best\_bid\_qty + best\_ask\_qty) \# Computes the trading impulse last\_qty \= 0 for last\_trade in last\_trades: if last\_trade.ev & BUY\_EVENT \== BUY\_EVENT: last\_qty += last\_trade.qty else: last\_qty \-= \-last\_trade.qty hbt.clear\_last\_trades(asset\_no) sum\_bbo\_qty\[i\] \= best\_bid\_qty + best\_ask\_qty i += 1 \# Uses the last 1-minute average BBO quantity as the denominator. trade\_impulse \= (tick\_size / 2.0) \* last\_qty / np.mean(sum\_bbo\_qty\[max(0, i \- 600):i\]) fair\_price \= book\_pressure + trade\_impulse \* trade\_impulse\_adj skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty \# The personalized price that considers skewing based on inventory risk is introduced, \# which is described in the well-known Stokov-Avalleneda market-making paper. \# https://math.nyu.edu/~avellane/HighFrequencyTrading.pdf reservation\_price \= fair\_price \- skew \* normalized\_position \# Since our price is skewed, it may cross the spread. To ensure market making and avoid crossing the spread, \# limit the price to the best bid and best ask. bid\_price \= np.minimum(reservation\_price \- half\_spread, best\_bid) ask\_price \= np.maximum(reservation\_price + half\_spread, best\_ask) \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[12\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[13\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 13s, sys: 7.99 s, total: 8min 21s Wall time: 6min 48s \[14\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[14\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 13.831429 | 20.083391 | 2.503742 | 0.114379 | 113.35505 | 32.174754 | 21.889804 | 0.001255 | 2.828 | \[15\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_17_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_17_0.png) You can also adjust `trade_impulse_adj` to modify the impact of the trade impulse. Alternatively, you can explore other ways to compute the trade impulse, such as `(best_bid * best_ask_qty + best_ask * best_bid_qty + last_px * last_qty) / (best_bid_qty + best_ask_qty + last_qty)`, VWAP, etc. Pure Queue-Based Model[](https://hftbacktest.readthedocs.io/en/py-v2.3.0/tutorials/Queue-Based%20Market%20Making%20in%20Large%20Tick%20Size%20Assets.html#Pure-Queue-Based-Model "Link to this heading") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- One possible reason for this strategy’s profitability is the limited price movement due to the large tick size. For instance, CRVUSDT has a tick size of 38 basis points (0.001 / 0.26 \* 10,000), which is comparatively very larger than BTCUSDT, where the tick size is approximately 0.018 basis points (0.1 / 54,000 \* 10,000). This also highlights the importance of queue position modeling in fill simulations for assets with large tick sizes. In the CRVUSDT charts shown above, observing the trading activities, you can see that most trades occur at the best bid and ask prices, with little change in the overall price level. This suggests an opportunity to adjust the microstructure signal into a purely queue-based signal. For example, if there is sufficient quantity to maintain the price level, preventing it from moving adversely, we can choose to maintain our quote. Let’s explore how this can be implemented in a simplified form. \[16\]: @njit def mm\_strategy(hbt, recorder): asset\_no \= 0 tick\_size \= hbt.depth(asset\_no).tick\_size order\_qty \= 1 grid\_num \= 10 max\_position \= grid\_num \* order\_qty \# Our half spread is just half a tick size, \# but it's considered a round-off error, so we use 0.49, which is slightly less than 0.5. half\_spread \= tick\_size \* 0.49 grid\_interval \= tick\_size skew\_adj \= 1.0 qty\_threshold \= 250\_000 \# Running interval in nanoseconds. while hbt.elapse(100\_000\_000) \== 0: \# Clears cancelled, filled or expired orders. hbt.clear\_inactive\_orders(asset\_no) depth \= hbt.depth(asset\_no) position \= hbt.position(asset\_no) orders \= hbt.orders(asset\_no) last\_trades \= hbt.last\_trades(asset\_no) best\_bid \= depth.best\_bid best\_ask \= depth.best\_ask best\_bid\_qty \= depth.bid\_depth\[depth.best\_bid\_tick\] best\_ask\_qty \= depth.ask\_depth\[depth.best\_ask\_tick\] skew \= half\_spread / grid\_num \* skew\_adj normalized\_position \= position / order\_qty skew\_val \= skew \* normalized\_position if best\_bid\_qty < qty\_threshold and skew\_val \> 0: bid\_price \= best\_bid \- tick\_size else: bid\_price \= best\_bid if best\_ask\_qty < qty\_threshold and skew\_val < 0: ask\_price \= best\_ask + tick\_size else: ask\_price \= best\_ask \# Aligns the prices to the grid. bid\_price \= np.floor(bid\_price / grid\_interval) \* grid\_interval ask\_price \= np.ceil(ask\_price / grid\_interval) \* grid\_interval #-------------------------------------------------------- \# Updates quotes. \# Creates a new grid for buy orders. new\_bid\_orders \= Dict.empty(np.uint64, np.float64) if position < max\_position and np.isfinite(bid\_price): for i in range(grid\_num): bid\_price\_tick \= round(bid\_price / tick\_size) \# order price in tick is used as order id. new\_bid\_orders\[uint64(bid\_price\_tick)\] \= bid\_price bid\_price \-= grid\_interval \# Creates a new grid for sell orders. new\_ask\_orders \= Dict.empty(np.uint64, np.float64) if position \> \-max\_position and np.isfinite(ask\_price): for i in range(grid\_num): ask\_price\_tick \= round(ask\_price / tick\_size) \# order price in tick is used as order id. new\_ask\_orders\[uint64(ask\_price\_tick)\] \= ask\_price ask\_price += grid\_interval order\_values \= orders.values(); while order\_values.has\_next(): order \= order\_values.get() \# Cancels if a working order is not in the new grid. if order.cancellable: if ( (order.side \== BUY and order.order\_id not in new\_bid\_orders) or (order.side \== SELL and order.order\_id not in new\_ask\_orders) ): hbt.cancel(asset\_no, order.order\_id, False) for order\_id, order\_price in new\_bid\_orders.items(): \# Posts a new buy order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_buy\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) for order\_id, order\_price in new\_ask\_orders.items(): \# Posts a new sell order if there is no working order at the price on the new grid. if order\_id not in orders: hbt.submit\_sell\_order(asset\_no, order\_id, order\_price, order\_qty, GTX, LIMIT, False) \# Records the current state for stat calculation. recorder.record(hbt) return True \[17\]: hbt \= ROIVectorMarketDepthBacktest(\[asset\]) recorder \= Recorder(1, 100\_000\_000) \[18\]: %%time mm\_strategy(hbt, recorder.recorder) \_ \= hbt.close() CPU times: user 8min 16s, sys: 8.52 s, total: 8min 24s Wall time: 6min 51s \[19\]: stats \= LinearAssetRecord(recorder.get(0)).stats() stats.summary() \[19\]: shape: (1, 11) | start | end | SR | Sortino | Return | MaxDrawdown | DailyNumberOfTrades | DailyTradingValue | ReturnOverMDD | ReturnOverTrade | MaxPositionValue | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | datetime\[μs\] | datetime\[μs\] | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2024-07-01 00:00:00 | 2024-08-31 23:59:50 | 13.199337 | 18.042325 | 3.95075 | 0.18009 | 1840.60021 | 509.758968 | 21.937611 | 0.000125 | 3.6905 | \[20\]: stats.plot() ![../_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_24_0.png](https://hftbacktest.readthedocs.io/en/py-v2.3.0/_images/tutorials_Queue-Based_Market_Making_in_Large_Tick_Size_Assets_24_0.png) You can also explore more sophisticated approaches, such as dynamically controlling the `qty_threshold` and integrating it with the skew value, for example, `qty_threshold * (1 ± skew_val)`, similar to how skew is applied to the price. In other words, in the previous example, the spread is set in terms of price, but you can set the spread in terms of queue such as the queue position, the queue behind the order, the total queue, etc. Additionally, instead of reacting at fixed intervals, it may be more effective to respond to each incoming feed. This allows for faster reactions when the quantity at the BBO decreases rapidly, helping to avoid adverse selection. You can test this approach using the `wait_next_feed` method. ---