Getting Started

Printing the best bid and the best ask

[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

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

[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

[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)

[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

[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

[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

[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

[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

[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

[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

[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]:
<Axes: >
../_images/tutorials_Getting_Started_43_1.png

Printing stats

[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