Skip to content

Commit

Permalink
Fix the issue regarding order modification
Browse files Browse the repository at this point in the history
  • Loading branch information
nkaz001 committed May 18, 2023
1 parent 2a085dc commit ee21475
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 29 deletions.
3 changes: 2 additions & 1 deletion hftbacktest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
IdentityProbQueueModel as IdentityProbQueueModel_,
SquareProbQueueModel as SquareProbQueueModel_
)
from .order import BUY, SELL, NONE, NEW, EXPIRED, FILLED, CANCELED, GTC, GTX, Order, OrderBus
from .order import BUY, SELL, NONE, NEW, EXPIRED, FILLED, CANCELED, MODIFY, GTC, GTX, Order, OrderBus
from .proc.local import Local
from .proc.nopartialfillexchange import NoPartialFillExchange
from .proc.partialfillexchange import PartialFillExchange
Expand Down Expand Up @@ -87,6 +87,7 @@
'EXPIRED',
'FILLED',
'CANCELED',
'MODIFY',

# Time-In-Force
'GTC',
Expand Down
6 changes: 3 additions & 3 deletions hftbacktest/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ def modify(self, order_id: int64, price: float64, qty: float64, wait: boolean =
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.
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.
Args:
order_id: Order ID to modify.
Expand Down
7 changes: 4 additions & 3 deletions hftbacktest/proc/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,14 @@ def modify_order(self, order_id, price, qty, current_timestamp):
if order.req != NONE:
raise ValueError('the given order cannot be modified because there is a ongoing request.')

order.req = MODIFY

order = order.copy()
order.price_tick = round(price / self.depth.tick_size)
order.qty = qty
order.req = MODIFY
exch_recv_timestamp = current_timestamp + self.order_latency.entry(current_timestamp, order, self)

self.orders[order.order_id] = order
self.orders_to.append(order.copy(), exch_recv_timestamp)
self.orders_to.append(order, exch_recv_timestamp)

def cancel(self, order_id, current_timestamp):
order = self.orders.get(order_id)
Expand Down
18 changes: 9 additions & 9 deletions hftbacktest/proc/nopartialfillexchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def _process_recv_order(self, order, recv_timestamp, wait_resp, next_timestamp):
resp_timestamp = self.__ack_new(order, recv_timestamp)

# Process a modify order.
if order.req == MODIFY:
elif order.req == MODIFY:
order.req = NONE
resp_timestamp = self.__ack_modify(order, recv_timestamp)

Expand Down Expand Up @@ -326,22 +326,22 @@ def __ack_modify(self, order, timestamp):
if exch_order.side == BUY:
# Check if the buy order price is greater than or equal to the current best ask.
if exch_order.price_tick >= self.depth.best_ask_tick:
del self.buy_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]

if exch_order.time_in_force == GTX:
exch_order.status = EXPIRED
del self.buy_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]
else:
# Take the market.
return self.__fill(
exch_order,
timestamp,
False,
exec_price_tick=self.depth.best_ask_tick,
delete_order=True
delete_order=False
)
else:
# The exchange accepts this order.
self.orders[exch_order.order_id] = exch_order
if prev_price_tick != exch_order.price_tick:
del self.buy_orders[prev_price_tick][exch_order.order_id]
o = self.buy_orders.setdefault(
Expand All @@ -356,22 +356,22 @@ def __ack_modify(self, order, timestamp):
else:
# Check if the sell order price is less than or equal to the current best bid.
if exch_order.price_tick <= self.depth.best_bid_tick:
del self.sell_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]

if exch_order.time_in_force == GTX:
exch_order.status = EXPIRED
del self.sell_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]
else:
# Take the market.
return self.__fill(
exch_order,
timestamp,
False,
exec_price_tick=self.depth.best_bid_tick,
delete_order=True
delete_order=False
)
else:
# The exchange accepts this order.
self.orders[exch_order.order_id] = order
if prev_price_tick != exch_order.price_tick:
del self.sell_orders[prev_price_tick][exch_order.order_id]
o = self.sell_orders.setdefault(
Expand Down
59 changes: 46 additions & 13 deletions hftbacktest/proc/partialfillexchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
PARTIALLY_FILLED,
MODIFY,
GTX,
GTC,
FOK,
IOC,
NONE,
Expand Down Expand Up @@ -93,7 +94,7 @@ def _process_recv_order(self, order, recv_timestamp, wait_resp, next_timestamp):
resp_timestamp = self.__ack_new(order, recv_timestamp)

# Process a modify order.
if order.req == MODIFY:
elif order.req == MODIFY:
order.req = NONE
resp_timestamp = self.__ack_modify(order, recv_timestamp)

Expand Down Expand Up @@ -514,22 +515,38 @@ def __ack_modify(self, order, timestamp):
if exch_order.side == BUY:
# Check if the buy order price is greater than or equal to the current best ask.
if exch_order.price_tick >= self.depth.best_ask_tick:
del self.buy_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]

if exch_order.time_in_force == GTX:
exch_order.status = EXPIRED
del self.buy_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]
else:
elif exch_order.time_in_force == GTC:
# Take the market.
for t in range(self.depth.best_ask_tick, exch_order.price_tick):
exec_qty = min(self.depth.ask_depth[t], exch_order.qty)
local_recv_timestamp = self.__fill(
exch_order,
exec_qty,
timestamp,
False,
exec_price_tick=t,
delete_order=False
)
if exch_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(
exch_order,
exch_order.leaves_qty,
timestamp,
False,
exec_price_tick=self.depth.best_ask_tick,
delete_order=True
exec_price_tick=exch_order.price_tick,
delete_order=False
)
else:
# The exchange accepts this order.
self.orders[exch_order.order_id] = exch_order
if prev_price_tick != exch_order.price_tick:
del self.buy_orders[prev_price_tick][exch_order.order_id]
o = self.buy_orders.setdefault(
Expand All @@ -544,22 +561,38 @@ def __ack_modify(self, order, timestamp):
else:
# Check if the sell order price is less than or equal to the current best bid.
if exch_order.price_tick <= self.depth.best_bid_tick:
del self.sell_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]

if exch_order.time_in_force == GTX:
exch_order.status = EXPIRED
del self.sell_orders[prev_price_tick][exch_order.order_id]
del self.orders[exch_order.order_id]
else:
elif exch_order.time_in_force == GTC:
# Take the market.
for t in range(self.depth.best_bid_tick, exch_order.price_tick, -1):
exec_qty = min(self.depth.bid_depth[t], exch_order.qty)
local_recv_timestamp = self.__fill(
exch_order,
exec_qty,
timestamp,
False,
exec_price_tick=t,
delete_order=False
)
if exch_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(
exch_order,
exch_order.leaves_qty,
timestamp,
False,
exec_price_tick=self.depth.best_bid_tick,
delete_order=True
exec_price_tick=exch_order.price_tick,
delete_order=False
)
else:
# The exchange accepts this order.
self.orders[exch_order.order_id] = order
if prev_price_tick != exch_order.price_tick:
del self.sell_orders[prev_price_tick][exch_order.order_id]
o = self.sell_orders.setdefault(
Expand Down

0 comments on commit ee21475

Please sign in to comment.