From fab7224b5526a56434770bc3f9e2218df56bea55 Mon Sep 17 00:00:00 2001 From: nkaz001 Date: Wed, 14 Aug 2024 08:00:43 -0400 Subject: [PATCH] docs: update the README and the example. --- README.rst | 148 ++++++++++++++++++++----------------- docs/index.rst | 174 +++++++++++++++++++++++--------------------- examples/example.py | 34 +++++---- 3 files changed, 191 insertions(+), 165 deletions(-) diff --git a/README.rst b/README.rst index b3606b8b..7c12157b 100644 --- a/README.rst +++ b/README.rst @@ -4,11 +4,6 @@ HftBacktest |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| -**The master branch is switched to hftbacktest-2.0.0-alpha, which uses the Rust implementation as the backend. If you want to see the current version 1.8.4, please check out the corresponding tag.** - -* `Browse v1.8.4 `_ -* `Docs v1.8.4 `_ - High-Frequency Trading Backtesting Tool ======================================= @@ -28,10 +23,6 @@ support the following features. * 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) -Example: 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. - Documentation ============= @@ -60,6 +51,8 @@ Data Source & Format Please see `Data `_ or `Data Preparation `_. +You can also find some data `here `_, hosted by the supporter. + A Quick Example --------------- @@ -68,83 +61,98 @@ Get a glimpse of what backtesting with hftbacktest looks like with these code sn .. code-block:: python @njit - def simple_two_sided_quote(hbt): - max_position = 5 - half_spread = hbt.tick_size * 20 - skew = 1 - order_qty = 0.1 - last_order_id = -1 - order_id = 0 + def market_making_algo(hbt): asset_no = 0 + tick_size = hbt.depth(asset_no).tick_size + lot_size = hbt.depth(asset_no).lot_size - # Checks every 0.1s - while hbt.elapse(100_000_000) == 0: - # Clears cancelled, filled or expired orders. + # in nanoseconds + while hbt.elapse(10_000_000) == 0: hbt.clear_inactive_orders(asset_no) - # Gets the market depth. + 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) - # Obtains the current mid-price and computes the reservation price. mid_price = (depth.best_bid + depth.best_ask) / 2.0 - reservation_price = mid_price - skew * hbt.position(asset_no) * depth.tick_size - buy_order_price = reservation_price - half_spread - sell_order_price = reservation_price + half_spread + # 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 - last_order_id = -1 - # Cancel all outstanding orders - orders = hbt.orders(asset_no) - values = orders.values() - while True: - order = values.next() - if order is None: - break - if order.cancellable: - hbt.cancel(asset_no, order.order_id) - last_order_id = order.order_id + 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) - # 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(asset_no, last_order_id) + order_qty = np.round(notional_qty / mid_price / lot_size) * lot_size - # Clears cancelled, filled or expired orders. - hbt.clear_inactive_orders(asset_no) + # Elapses a process time. + if not hbt.elapse(1_000_000) != 0: + return False - last_order_id = -1 - if hbt.position < max_position: - # Submits a new post-only limit bid order. - order_id += 1 - hbt.submit_buy_order( - asset_no, - order_id, - buy_order_price, - order_qty, - GTX, - LIMIT, - 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 hbt.position > -max_position: - # Submits a new post-only limit ask order. - order_id += 1 - hbt.submit_sell_order( - asset_no, - order_id, - sell_order_price, - order_qty, - GTX, - LIMIT, - False - ) + 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: - hbt.wait_order_response(asset_no, last_order_id) + # 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 @@ -166,6 +174,10 @@ 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 `_. diff --git a/docs/index.rst b/docs/index.rst index 0039072e..ed6e1e5c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,11 +7,6 @@ HftBacktest |codeql| |python| |pypi| |downloads| |rustc| |crates| |license| |docs| |roadmap| |github| -**The master branch is switched to hftbacktest-2.0.0-alpha, which uses the Rust implementation as the backend. If you want to see the current version 1.8.4, please check out the corresponding tag.** - -* `Browse v1.8.4 `_ -* `Docs v1.8.4 `_ - High-Frequency Trading Backtesting Tool ======================================= @@ -31,10 +26,6 @@ support the following features. * 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) -Example: 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. - Documentation ============= @@ -61,7 +52,9 @@ Or you can clone the latest development version from the Git repository with: Data Source & Format -------------------- -Please see :doc:`Data `_ or :doc:`Data Preparation `. +Please see `Data `_ or `Data Preparation `_. + +You can also find some data `here `_, hosted by the supporter. A Quick Example --------------- @@ -71,107 +64,126 @@ Get a glimpse of what backtesting with hftbacktest looks like with these code sn .. code-block:: python @njit - def simple_two_sided_quote(hbt): - max_position = 5 - half_spread = hbt.tick_size * 20 - skew = 1 - order_qty = 0.1 - last_order_id = -1 - order_id = 0 + def market_making_algo(hbt): asset_no = 0 + tick_size = hbt.depth(asset_no).tick_size + lot_size = hbt.depth(asset_no).lot_size - # Checks every 0.1s - while hbt.elapse(100_000_000) == 0: - # Clears cancelled, filled or expired orders. + # in nanoseconds + while hbt.elapse(10_000_000) == 0: hbt.clear_inactive_orders(asset_no) - # Gets the market depth. + 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) - # Obtains the current mid-price and computes the reservation price. mid_price = (depth.best_bid + depth.best_ask) / 2.0 - reservation_price = mid_price - skew * hbt.position(asset_no) * depth.tick_size - buy_order_price = reservation_price - half_spread - sell_order_price = reservation_price + half_spread + # 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 - last_order_id = -1 - # Cancel all outstanding orders - orders = hbt.orders(asset_no) - values = orders.values() - while True: - order = values.next() - if order is None: - break - if order.cancellable: - hbt.cancel(asset_no, order.order_id) - last_order_id = order.order_id + 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) - # 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(asset_no, last_order_id) + order_qty = np.round(notional_qty / mid_price / lot_size) * lot_size - # Clears cancelled, filled or expired orders. - hbt.clear_inactive_orders(asset_no) + # Elapses a process time. + if not hbt.elapse(1_000_000) != 0: + return False - last_order_id = -1 - if hbt.position < max_position: - # Submits a new post-only limit bid order. - order_id += 1 - hbt.submit_buy_order( - asset_no, - order_id, - buy_order_price, - order_qty, - GTX, - LIMIT, - 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 hbt.position > -max_position: - # Submits a new post-only limit ask order. - order_id += 1 - hbt.submit_sell_order( - asset_no, - order_id, - sell_order_price, - order_qty, - GTX, - LIMIT, - False - ) + 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: - hbt.wait_order_response(asset_no, last_order_id) + # 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 ========= -* :doc:`Data Preparation ` -* :doc:`Getting Started ` -* :doc:`Working with Market Depth and Trades ` -* :doc:`Integrating Custom Data ` -* :doc:`Making Multiple Markets - Introduction ` -* :doc:`High-Frequency Grid Trading ` -* :doc:`Impact of Order Latency ` -* :doc:`Order Latency Data ` -* :doc:`Guéant–Lehalle–Fernandez-Tapia Market Making Model and Grid Trading ` -* :doc:`Making Multiple Markets ` -* :doc:`Risk Mitigation through Price Protection in Extreme Market Conditions ` +* `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 :doc:`migration guide `. +Please see the `migration guide `_. Roadmap ======= diff --git a/examples/example.py b/examples/example.py index dc7b128e..673796ff 100644 --- a/examples/example.py +++ b/examples/example.py @@ -28,11 +28,12 @@ def market_making_algo(hbt): c = 1 hs = 1 - # alpha, it can be a combination of several indicators. + # Alpha, it can be a combination of several indicators. forecast = 0 - # in hft, it could be a measurement of short-term market movement such as high - low of the last x-min. + # 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 also can be a combination of several risks. + # Delta risk, it can be a combination of several risks. position = hbt.position(asset_no) risk = (c + volatility) * position half_spread = (c + volatility) * hs @@ -47,14 +48,13 @@ def market_making_algo(hbt): # fair value pricing = mid_price + a * forecast # or underlying(correlated asset) + adjustment(basis + cost + etc) + a * forecast # risk skewing = -b * risk - new_bid = mid_price + a * forecast - b * risk - half_spread - new_ask = mid_price + a * forecast - b * risk + half_spread + reservation_price = mid_price + a * forecast - b * risk + new_bid = reservation_price - half_spread + new_ask = reservation_price + half_spread - new_bid_tick = np.round(new_bid / tick_size) - new_ask_tick = np.round(new_ask / tick_size) + 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) - new_bid = new_bid_tick * hbt.tick_size - new_ask = new_ask_tick * hbt.tick_size order_qty = np.round(notional_qty / mid_price / lot_size) * lot_size # Elapses a process time. @@ -64,20 +64,22 @@ def market_making_algo(hbt): 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 position * mid_price > max_notional_position: + if order.price_tick == new_bid_tick or buy_limit_exceeded: update_bid = False - elif order.cancellable or position * mid_price > max_notional_position: + if order.cancellable and (update_bid or buy_limit_exceeded): hbt.cancel(asset_no, order.order_id, False) last_order_id = order.order_id - if order.side == SELL: - if order.price_tick == new_ask_tick or position * mid_price < -max_notional_position: + elif order.side == SELL: + if order.price_tick == new_ask_tick or sell_limit_exceeded: update_ask = False - if order.cancellable or position * mid_price < -max_notional_position: + if order.cancellable and (update_ask or sell_limit_exceeded): hbt.cancel(asset_no, order.order_id, False) last_order_id = order.order_id @@ -87,12 +89,12 @@ def market_making_algo(hbt): 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, order_qty, GTX, LIMIT, False) + 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, order_qty, GTX, LIMIT, False) + 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.