Skip to content

Commit 441245c

Browse files
committed
Increased priority for order update events so they get dispatched before other events like bar events.
1 parent fbb6404 commit 441245c

4 files changed

Lines changed: 67 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Bug fixes
66

77
* Backtesting stop orders were being canceled if the stop price was not hit.
8+
* Backtesting order updates were sometimes being processed after bar events. Order update events should get processed first.
89

910
### Misc
1011

basana/backtesting/order_mgr.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from basana.backtesting import account_balances, config, errors, fees, helpers, lending, loan_mgr, liquidity, prices
2626
from basana.backtesting.orders import Order, OrderInfo
2727
from basana.backtesting.value_map import ValueMap, ValueMapDict
28-
from basana.core import dispatcher, helpers as core_helpers, logs
28+
from basana.core import dispatcher, event, helpers as core_helpers, logs
2929
from basana.core.bar import Bar, BarEvent
3030
from basana.core.enums import OrderOperation
3131
from basana.core.pair import Pair
@@ -36,6 +36,7 @@
3636

3737
LiquidityStrategyFactory = Callable[[], liquidity.LiquidityStrategy]
3838
OrderEventHandler = Callable[["OrderEvent"], Awaitable[Any]]
39+
ORDER_UPDATE_PRIORITY = event.DEFAULT_EVENT_SOURCE_PRIORITY + 1
3940

4041

4142
@dataclasses.dataclass
@@ -81,7 +82,7 @@ def __init__(self, exchange_ctx: ExchangeContext, immediate_order_processing: bo
8182
)
8283
self._orders = helpers.ExchangeObjectContainer[Order]()
8384
self._holds_by_order: Dict[str, ValueMap] = {}
84-
self._order_updates = core_helpers.LazyProxy(bs.FifoQueueEventSource)
85+
self._order_updates = core_helpers.LazyProxy(lambda: bs.FifoQueueEventSource(priority=ORDER_UPDATE_PRIORITY))
8586

8687
def on_bar_event(self, bar_event: BarEvent):
8788
liquidity_strategy = self._liquidity_strategies[bar_event.bar.pair]

basana/core/event.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
from . import dt
2323

2424

25-
MIN_EVENTSOURCE_PRIORITY = -1000
26-
MAX_EVENTSOURCE_PRIORITY = 1000
25+
MIN_EVENT_SOURCE_PRIORITY = -1000
26+
MAX_EVENT_SOURCE_PRIORITY = 1000
27+
DEFAULT_EVENT_SOURCE_PRIORITY = 0
2728

2829

2930
class Producer:
@@ -84,17 +85,19 @@ class EventSource(metaclass=abc.ABCMeta):
8485
:param producer: An optional producer associated with this event source.
8586
"""
8687

87-
def __init__(self, producer: Optional[Producer] = None):
88+
def __init__(self, producer: Optional[Producer] = None, priority: int = DEFAULT_EVENT_SOURCE_PRIORITY):
89+
assert priority >= MIN_EVENT_SOURCE_PRIORITY and priority <= MAX_EVENT_SOURCE_PRIORITY, "Invalid priority"
90+
8891
self.producer = producer
89-
self._priority = 0
92+
self._priority = priority
9093

9194
@property
9295
def priority(self):
9396
return self._priority
9497

9598
@priority.setter
9699
def priority(self, value: int):
97-
assert value >= MIN_EVENTSOURCE_PRIORITY and value <= MAX_EVENTSOURCE_PRIORITY, "Invalid priority"
100+
assert value >= MIN_EVENT_SOURCE_PRIORITY and value <= MAX_EVENT_SOURCE_PRIORITY, "Invalid priority"
98101
self._priority = value
99102

100103
@abc.abstractmethod
@@ -113,8 +116,11 @@ class FifoQueueEventSource(EventSource):
113116
:param producer: An optional producer associated with this event source.
114117
:param events: An optional list of initial events.
115118
"""
116-
def __init__(self, producer: Optional[Producer] = None, events: List[Event] = []):
117-
super().__init__(producer)
119+
def __init__(
120+
self, producer: Optional[Producer] = None, events: List[Event] = [],
121+
priority: int = DEFAULT_EVENT_SOURCE_PRIORITY
122+
):
123+
super().__init__(producer, priority)
118124
self._queue = deque(events)
119125

120126
def push(self, event: Event):

tests/test_backtesting_exchange_orders.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ def test_create_get_and_cancel_order(backtesting_dispatcher):
4747
bar_events = []
4848

4949
async def on_order_updated(event: exchange.OrderEvent):
50-
order_events.append(event.order)
50+
order_events.append(event)
5151

5252
async def on_bar(bar_event):
53+
bar_events.append(bar_event)
54+
5355
order_request = requests.MarketOrder(OrderOperation.BUY, pair, Decimal("1"))
5456
created_order = await e.create_order(order_request)
5557
assert created_order is not None
@@ -87,11 +89,54 @@ async def on_bar(bar_event):
8789
assert sum(e._balances.holds.values()) == 0
8890
assert len(e._order_mgr._holds_by_order) == 0
8991

92+
async def impl():
93+
e.subscribe_to_bar_events(pair, on_bar)
94+
e.subscribe_to_order_events(on_order_updated)
95+
96+
e.add_bar_source(
97+
event.FifoQueueEventSource(events=[
98+
bar.BarEvent(
99+
dt.local_datetime(2001, 1, 3),
100+
bar.Bar(
101+
dt.local_datetime(2001, 1, 2), pair,
102+
Decimal(5), Decimal(10), Decimal(1), Decimal(5), Decimal(100)
103+
)
104+
),
105+
])
106+
)
107+
108+
await backtesting_dispatcher.run()
109+
110+
assert len(bar_events) == 1
111+
assert len(order_events) == 2
112+
assert order_events[0].order.is_open is True
113+
assert order_events[1].order.is_open is False
114+
115+
asyncio.run(impl())
116+
117+
118+
def test_order_updates_have_priority(backtesting_dispatcher):
119+
e = exchange.Exchange(
120+
backtesting_dispatcher,
121+
{"USD": Decimal("50000")}
122+
)
123+
pair = Pair("BTC", "USD")
124+
order_events = []
125+
bar_events = []
126+
all_events = []
127+
128+
async def on_order_updated(event: exchange.OrderEvent):
129+
order_events.append(event)
130+
all_events.append(event)
131+
132+
async def on_bar(bar_event):
90133
bar_events.append(bar_event)
134+
all_events.append(bar_event)
91135

92136
async def impl():
93137
e.subscribe_to_bar_events(pair, on_bar)
94138
e.subscribe_to_order_events(on_order_updated)
139+
95140
e.add_bar_source(
96141
event.FifoQueueEventSource(events=[
97142
bar.BarEvent(
@@ -104,12 +149,13 @@ async def impl():
104149
])
105150
)
106151

152+
await e.create_limit_order(OrderOperation.BUY, pair, Decimal(1), Decimal(10))
107153
await backtesting_dispatcher.run()
108154

109155
assert len(bar_events) == 1
110-
assert len(order_events) == 2
111-
assert order_events[0].is_open is True
112-
assert order_events[1].is_open is False
156+
assert len(order_events) == 1
157+
assert order_events[0].order.amount_filled == Decimal(1)
158+
assert all_events == order_events + bar_events
113159

114160
asyncio.run(impl())
115161

0 commit comments

Comments
 (0)