Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
nkaz001 committed Mar 31, 2023
1 parent cfa35ed commit 5be4b2f
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 31 deletions.
10 changes: 6 additions & 4 deletions hftbacktest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from .reader import COL_EVENT, COL_EXCH_TIMESTAMP, COL_LOCAL_TIMESTAMP, COL_SIDE, COL_PRICE, COL_QTY, \
DEPTH_EVENT, DEPTH_CLEAR_EVENT, DEPTH_SNAPSHOT_EVENT, TRADE_EVENT, DataReader, Cache
from .order import BUY, SELL, NONE, NEW, EXPIRED, FILLED, CANCELED, GTC, GTX, Order, OrderBus
from .backtest import SingleInstHftBacktest
from .backtest import SingleAssetHftBacktest
from .data import validate_data, correct_local_timestamp, correct_exch_timestamp, correct
from .proc.local import Local
from .proc.nopartialfillexchange import NoPartialFillExch
from .proc.nopartialfillexchange import NoPartialFillExchange
from .proc.partialfillexchange import PartialFillExchange
from .marketdepth import MarketDepth
from .state import State
from .models.latencies import FeedLatency, ConstantLatency, ForwardFeedLatency, BackwardFeedLatency, IntpOrderLatency
Expand All @@ -21,6 +22,7 @@
'NONE', 'NEW', 'EXPIRED', 'FILLED', 'CANCELED',
'GTC', 'GTX',
'Order', 'HftBacktest',
'NoPartialFillExchange', 'PartialFillExchange',
'ConstantLatency', 'FeedLatency', 'ForwardFeedLatency', 'BackwardFeedLatency', 'IntpOrderLatency',
'Linear', 'Inverse',
'RiskAverseQueueModel', 'LogProbQueueModel', 'IdentityProbQueueModel', 'SquareProbQueueModel',
Expand Down Expand Up @@ -163,7 +165,7 @@ def HftBacktest(
)

if exchange_model is None:
exchange_model = NoPartialFillExch
exchange_model = NoPartialFillExchange

exch = exchange_model(
exch_reader,
Expand All @@ -175,4 +177,4 @@ def HftBacktest(
queue_model
)

return SingleInstHftBacktest(local, exch)
return SingleAssetHftBacktest(local, exch)
6 changes: 3 additions & 3 deletions hftbacktest/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .reader import WAIT_ORDER_RESPONSE_NONE, COL_LOCAL_TIMESTAMP, UNTIL_END_OF_DATA


class SingleInstHftBacktest_:
class SingleAssetHftBacktest_:
def __init__(self, local, exch):
self.local = local
self.exch = exch
Expand Down Expand Up @@ -204,11 +204,11 @@ def goto(self, timestamp, wait_order_response=WAIT_ORDER_RESPONSE_NONE):
return True


def SingleInstHftBacktest(local, exch):
def SingleAssetHftBacktest(local, exch):
jitted = jitclass(spec=[
('run', boolean),
('current_timestamp', int64),
('local', typeof(local)),
('exch', typeof(exch)),
])(SingleInstHftBacktest_)
])(SingleAssetHftBacktest_)
return jitted(local, exch)
4 changes: 2 additions & 2 deletions hftbacktest/models/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def depth(self, order, prev_qty, new_qty, proc):
order.q[0] = min(order.q[0], new_qty)

def is_filled(self, order, proc):
return -order.q[0]
return round(order.q[0] / proc.lot_size) < 0


class ProbQueueModel:
Expand Down Expand Up @@ -64,7 +64,7 @@ def depth(self, order, prev_qty, new_qty, proc):
order.q[0] = min(est_front, new_qty)

def is_filled(self, order, proc):
return -order.q[0]
return round(order.q[0] / proc.lot_size) < 0

def prob(self, front, back):
return np.divide(self.f(back), self.f(back) + self.f(front))
Expand Down
22 changes: 13 additions & 9 deletions hftbacktest/proc/nopartialfillexchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
DEPTH_SNAPSHOT_EVENT, TRADE_EVENT


class NoPartialFillExch_(Proc):
class NoPartialFillExchange_(Proc):
def __init__(
self,
reader,
Expand Down Expand Up @@ -139,8 +139,7 @@ def __check_if_sell_filled(self, order, price_tick, qty, timestamp):
elif order.price_tick == price_tick:
# Update the order's queue position.
self.queue_model.trade(order, qty, self)
exec_qty = self.queue_model.is_filled(order, self)
if round(exec_qty / self.depth.lot_size) > 0:
if self.queue_model.is_filled(order, self):
self.__fill(order, timestamp, True)

def __check_if_buy_filled(self, order, price_tick, qty, timestamp):
Expand All @@ -149,8 +148,7 @@ def __check_if_buy_filled(self, order, price_tick, qty, timestamp):
elif order.price_tick == price_tick:
# Update the order's queue position.
self.queue_model.trade(order, qty, self)
exec_qty = self.queue_model.is_filled(order, self)
if round(exec_qty / self.depth.lot_size) > 0:
if self.queue_model.is_filled(order, self):
self.__fill(order, timestamp, True)

def on_new(self, order):
Expand Down Expand Up @@ -224,7 +222,10 @@ def __ack_new(self, order, timestamp):
else:
# The exchange accepts this order.
self.orders[order.order_id] = order
o = self.buy_orders.setdefault(order.price_tick, Dict.empty(int64, order_ladder_ty))
o = self.buy_orders.setdefault(
order.price_tick,
Dict.empty(int64, order_ladder_ty)
)
o[order.order_id] = order
# Initialize the order's queue position.
self.queue_model.new(order, self)
Expand All @@ -246,7 +247,10 @@ def __ack_new(self, order, timestamp):
else:
# The exchange accepts this order.
self.orders[order.order_id] = order
o = self.sell_orders.setdefault(order.price_tick, Dict.empty(int64, order_ladder_ty))
o = self.sell_orders.setdefault(
order.price_tick,
Dict.empty(int64, order_ladder_ty)
)
o[order.order_id] = order
# Initialize the order's queue position.
self.queue_model.new(order, self)
Expand Down Expand Up @@ -310,7 +314,7 @@ def __fill(
return local_recv_timestamp


def NoPartialFillExch(
def NoPartialFillExchange(
reader,
orders_to_local,
orders_from_local,
Expand All @@ -325,7 +329,7 @@ def NoPartialFillExch(
('buy_orders', DictType(int64, order_ladder_ty)),
('queue_model', typeof(queue_model))
]
)(NoPartialFillExch_)
)(NoPartialFillExchange_)
return jitted(
reader,
orders_to_local,
Expand Down
42 changes: 29 additions & 13 deletions hftbacktest/proc/partialfillexchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
from numba.typed.typeddict import Dict
from numba.types import DictType

import numpy as np

from .proc import Proc, proc_spec
from ..marketdepth import INVALID_MAX, INVALID_MIN
from ..order import BUY, SELL, NEW, CANCELED, FILLED, EXPIRED, PARTIALLY_FILLED, GTX, FOK, IOC, NONE, order_ladder_ty
from ..reader import COL_EVENT, COL_EXCH_TIMESTAMP, COL_SIDE, COL_PRICE, COL_QTY, DEPTH_CLEAR_EVENT, DEPTH_EVENT, \
DEPTH_SNAPSHOT_EVENT, TRADE_EVENT


class PartialFillExch_(Proc):
class PartialFillExchange_(Proc):
def __init__(
self,
reader,
Expand Down Expand Up @@ -146,11 +148,12 @@ def __check_if_sell_filled(self, order, price_tick, qty, timestamp):
elif order.price_tick == price_tick:
# Update the order's queue position.
self.queue_model.trade(order, qty, self)
exec_qty = self.queue_model.is_filled(order, self)
if round(exec_qty / self.depth.lot_size) > 0:
if self.queue_model.is_filled(order, self):
q_qty = np.ceil(-order.q[0] / self.depth.lot_size) * self.depth.lot_size
exec_qty = min(q_qty, qty, order.leaves_qty)
self.__fill(
order,
min(exec_qty, qty),
exec_qty,
timestamp,
True
)
Expand All @@ -166,11 +169,12 @@ def __check_if_buy_filled(self, order, price_tick, qty, timestamp):
elif order.price_tick == price_tick:
# Update the order's queue position.
self.queue_model.trade(order, qty, self)
exec_qty = self.queue_model.is_filled(order, self)
if round(exec_qty / self.depth.lot_size) > 0:
if self.queue_model.is_filled(order, self):
q_qty = np.ceil(-order.q[0] / self.depth.lot_size) * self.depth.lot_size
exec_qty = min(q_qty, qty, order.leaves_qty)
self.__fill(
order,
min(exec_qty, qty),
exec_qty,
timestamp,
True
)
Expand Down Expand Up @@ -261,7 +265,7 @@ def __ack_new(self, order, timestamp):
cum_qty = 0
for t in range(self.depth.best_ask_tick, order.price_tick + 1):
cum_qty += self.depth.ask_depth[t]
if cum_qty >= order.qty:
if round(cum_qty / self.depth.lot_size) >= round(order.qty / self.depth.lot_size):
execute = True
break
if execute:
Expand Down Expand Up @@ -309,6 +313,9 @@ def __ack_new(self, order, timestamp):
)
if order.status == FILLED:
return local_recv_timestamp
# The buy order cannot remain in the ask book, as it cannot affect the market depth during
# backtesting based on market-data replay. So, even though it simulates partial fill, if the order
# size is not small enough, it introduces unreality.
return self.__fill(
order,
order.leaves_qty,
Expand All @@ -320,7 +327,10 @@ def __ack_new(self, order, timestamp):
else:
# The exchange accepts this order.
self.orders[order.order_id] = order
o = self.buy_orders.setdefault(order.price_tick, Dict.empty(int64, order_ladder_ty))
o = self.buy_orders.setdefault(
order.price_tick,
Dict.empty(int64, order_ladder_ty)
)
o[order.order_id] = order
# Initialize the order's queue position.
self.queue_model.new(order, self)
Expand All @@ -337,7 +347,7 @@ def __ack_new(self, order, timestamp):
cum_qty = 0
for t in range(self.depth.best_bid_tick, order.price_tick - 1, -1):
cum_qty += self.depth.bid_depth[t]
if cum_qty >= order.qty:
if round(cum_qty / self.depth.lot_size) >= round(order.qty / self.depth.lot_size):
execute = True
break
if execute:
Expand Down Expand Up @@ -385,6 +395,9 @@ def __ack_new(self, order, timestamp):
)
if order.status == FILLED:
return local_recv_timestamp
# The sell order cannot remain in the bid book, as it cannot affect the market depth during
# backtesting based on market-data replay. So, even though it simulates partial fill, if the order
# size is not small enough, it introduces unreality.
return self.__fill(
order,
order.leaves_qty,
Expand All @@ -396,7 +409,10 @@ def __ack_new(self, order, timestamp):
else:
# The exchange accepts this order.
self.orders[order.order_id] = order
o = self.sell_orders.setdefault(order.price_tick, Dict.empty(int64, order_ladder_ty))
o = self.sell_orders.setdefault(
order.price_tick,
Dict.empty(int64, order_ladder_ty)
)
o[order.order_id] = order
# Initialize the order's queue position.
self.queue_model.new(order, self)
Expand Down Expand Up @@ -461,7 +477,7 @@ def __fill(
return local_recv_timestamp


def PartialFillExch(
def PartialFillExchange(
reader,
orders_to_local,
orders_from_local,
Expand All @@ -476,7 +492,7 @@ def PartialFillExch(
('buy_orders', DictType(int64, order_ladder_ty)),
('queue_model', typeof(queue_model))
]
)(PartialFillExch_)
)(PartialFillExchange_)
return jitted(
reader,
orders_to_local,
Expand Down

0 comments on commit 5be4b2f

Please sign in to comment.