onesecondtrader 0.49.0__tar.gz → 0.51.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/PKG-INFO +1 -1
  2. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/pyproject.toml +1 -1
  3. onesecondtrader-0.51.0/src/onesecondtrader/brokers/__init__.py +11 -0
  4. onesecondtrader-0.51.0/src/onesecondtrader/brokers/base.py +110 -0
  5. onesecondtrader-0.51.0/src/onesecondtrader/brokers/simulated.py +494 -0
  6. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/orders/base.py +4 -4
  7. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/orders/expirations.py +2 -2
  8. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/orders/fills.py +1 -1
  9. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/requests/base.py +1 -1
  10. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/requests/order_submission.py +1 -1
  11. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/responses/base.py +4 -4
  12. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/responses/cancellations.py +5 -5
  13. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/responses/modifications.py +5 -5
  14. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/responses/orders.py +5 -5
  15. onesecondtrader-0.51.0/src/onesecondtrader/messaging/__init__.py +11 -0
  16. onesecondtrader-0.51.0/src/onesecondtrader/messaging/eventbus.py +96 -0
  17. onesecondtrader-0.51.0/src/onesecondtrader/messaging/subscriber.py +153 -0
  18. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/rejection_reasons.py +5 -5
  19. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/LICENSE +0 -0
  20. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/README.md +0 -0
  21. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/__init__.py +0 -0
  22. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/__init__.py +0 -0
  23. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/base.py +0 -0
  24. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/market/__init__.py +0 -0
  25. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/market/bar_processed.py +0 -0
  26. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/market/bar_received.py +0 -0
  27. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/orders/__init__.py +0 -0
  28. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/requests/__init__.py +0 -0
  29. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/requests/order_cancellation.py +0 -0
  30. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/requests/order_modification.py +0 -0
  31. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/events/responses/__init__.py +0 -0
  32. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/indicators/__init__.py +0 -0
  33. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/indicators/base.py +0 -0
  34. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/indicators/market_fields.py +0 -0
  35. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/indicators/moving_averages.py +0 -0
  36. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/__init__.py +0 -0
  37. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/bar_fields.py +0 -0
  38. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/bar_period.py +0 -0
  39. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/order_types.py +0 -0
  40. {onesecondtrader-0.49.0 → onesecondtrader-0.51.0}/src/onesecondtrader/models/trade_sides.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onesecondtrader
3
- Version: 0.49.0
3
+ Version: 0.51.0
4
4
  Summary: The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place.
5
5
  License-File: LICENSE
6
6
  Author: Nils P. Kujath
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "onesecondtrader"
3
- version = "0.49.0"
3
+ version = "0.51.0"
4
4
  description = "The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place."
5
5
  authors = [
6
6
  {name = "Nils P. Kujath",email = "63961429+NilsKujath@users.noreply.github.com"}
@@ -0,0 +1,11 @@
1
+ """
2
+ Provides interfaces for order execution via a simulated broker and adapters to real venues.
3
+ """
4
+
5
+ from onesecondtrader.brokers.base import BrokerBase
6
+ from onesecondtrader.brokers.simulated import SimulatedBroker
7
+
8
+ __all__ = [
9
+ "BrokerBase",
10
+ "SimulatedBroker",
11
+ ]
@@ -0,0 +1,110 @@
1
+ import abc
2
+
3
+ from onesecondtrader import events, messaging
4
+
5
+
6
+ class BrokerBase(messaging.Subscriber):
7
+ """
8
+ Abstract base class for broker components.
9
+
10
+ A broker component receives order-related request events from the event bus and translates them into actions against an external execution venue or simulated environment.
11
+ Responses to these requests are published back onto the event bus.
12
+
13
+ This class defines the event-handling interface and subscription logic common to all broker implementations.
14
+ """
15
+
16
+ def __init__(self, event_bus: messaging.EventBus) -> None:
17
+ """
18
+ Initialize the broker and subscribe to order request events.
19
+
20
+ Parameters:
21
+ event_bus:
22
+ Event bus used for receiving order requests and publishing response events.
23
+ """
24
+ super().__init__(event_bus)
25
+
26
+ self._subscribe(
27
+ events.requests.OrderSubmissionRequest,
28
+ events.requests.OrderCancellationRequest,
29
+ events.requests.OrderModificationRequest,
30
+ )
31
+
32
+ @abc.abstractmethod
33
+ def connect(self) -> None:
34
+ """
35
+ Establish a connection to the external broker API.
36
+ For simulated broker, this method is a no-op.
37
+
38
+ Implementations are responsible for initializing any external resources required to submit, modify, or cancel orders.
39
+ """
40
+ pass
41
+
42
+ def disconnect(self) -> None:
43
+ """
44
+ Disconnect the external broker API and stop event processing.
45
+ For simulated broker, this method is a no-op.
46
+
47
+ This method shuts down the subscriber and releases associated resources.
48
+ """
49
+ self.shutdown()
50
+
51
+ def _on_event(self, event: events.EventBase) -> None:
52
+ """
53
+ Dispatch incoming order-related events to the appropriate handler.
54
+
55
+ Parameters:
56
+ event:
57
+ Incoming event received from the event bus.
58
+ """
59
+ match event:
60
+ case events.requests.OrderSubmissionRequest() as submit_order:
61
+ self._on_submit_order(submit_order)
62
+ case events.requests.OrderCancellationRequest() as cancel_order:
63
+ self._on_cancel_order(cancel_order)
64
+ case events.requests.OrderModificationRequest() as modify_order:
65
+ self._on_modify_order(modify_order)
66
+ case _:
67
+ return
68
+
69
+ @abc.abstractmethod
70
+ def _on_submit_order(self, event: events.requests.OrderSubmissionRequest) -> None:
71
+ """
72
+ Handle an order submission request.
73
+
74
+ Parameters:
75
+ event:
76
+ Order submission request event.
77
+ """
78
+ pass
79
+
80
+ @abc.abstractmethod
81
+ def _on_cancel_order(self, event: events.requests.OrderCancellationRequest) -> None:
82
+ """
83
+ Handle an order cancellation request.
84
+
85
+ Parameters:
86
+ event:
87
+ Order cancellation request event.
88
+ """
89
+ pass
90
+
91
+ @abc.abstractmethod
92
+ def _on_modify_order(self, event: events.requests.OrderModificationRequest) -> None:
93
+ """
94
+ Handle an order modification request.
95
+
96
+ Parameters:
97
+ event:
98
+ Order modification request event.
99
+ """
100
+ pass
101
+
102
+ def _respond(self, response_event: events.responses.ResponseBase) -> None:
103
+ """
104
+ Publish a response event to the event bus.
105
+
106
+ Parameters:
107
+ response_event:
108
+ Response event generated by the broker.
109
+ """
110
+ self._publish(response_event)
@@ -0,0 +1,494 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import uuid
5
+
6
+ from onesecondtrader import events, messaging, models
7
+ from onesecondtrader.brokers.base import BrokerBase
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class _PendingOrder:
12
+ """
13
+ Internal order state tracked by the simulated broker.
14
+
15
+ This structure represents broker-side pending order state and is distinct from order request events.
16
+ It is used to evaluate trigger conditions against incoming market bars and to generate fills when conditions are met.
17
+ """
18
+
19
+ order_id: uuid.UUID
20
+ symbol: str
21
+ order_type: models.OrderType
22
+ side: models.TradeSide
23
+ quantity: float
24
+ limit_price: float | None = None
25
+ stop_price: float | None = None
26
+
27
+
28
+ class SimulatedBroker(BrokerBase):
29
+ """
30
+ Event-driven simulated broker for backtesting.
31
+
32
+ The broker subscribes to order request events and market bar events.
33
+ Order requests are validated and accepted or rejected immediately.
34
+ Accepted orders are stored as pending broker-side state and evaluated against each incoming
35
+ bar.
36
+ When an order triggers, a fill event is published with a deterministic fill price model based on the bar's OHLC values.
37
+
38
+ The broker publishes response events using the event timestamp to preserve simulated time consistency.
39
+ """
40
+
41
+ commission_per_unit: float = 0.0
42
+ minimum_commission_per_order: float = 0.0
43
+
44
+ def __init__(self, event_bus: messaging.EventBus) -> None:
45
+ """
46
+ parameters:
47
+ event_bus:
48
+ Event bus used to receive order requests and market bars, and to publish broker responses and fills.
49
+ """
50
+ self._pending_market_orders: dict[uuid.UUID, _PendingOrder] = {}
51
+ self._pending_limit_orders: dict[uuid.UUID, _PendingOrder] = {}
52
+ self._pending_stop_orders: dict[uuid.UUID, _PendingOrder] = {}
53
+ self._pending_stop_limit_orders: dict[uuid.UUID, _PendingOrder] = {}
54
+
55
+ super().__init__(event_bus)
56
+ self._subscribe(events.market.BarReceived)
57
+
58
+ def connect(self) -> None:
59
+ """
60
+ Establish broker readiness.
61
+
62
+ The simulated broker has no external connectivity requirements.
63
+ This method is a no-op and exists to satisfy the broker interface.
64
+ """
65
+ pass
66
+
67
+ def _on_event(self, event: events.EventBase) -> None:
68
+ """
69
+ Dispatch incoming events.
70
+
71
+ Market bar events are routed to bar processing.
72
+ All other events are delegated to the broker base class for order request handling.
73
+
74
+ parameters:
75
+ event:
76
+ Incoming event received from the event bus.
77
+ """
78
+ match event:
79
+ case events.market.BarReceived() as bar:
80
+ self._on_bar(bar)
81
+ case _:
82
+ super()._on_event(event)
83
+
84
+ def _on_bar(self, event: events.market.BarReceived) -> None:
85
+ """
86
+ Process an incoming market bar.
87
+
88
+ Pending orders are evaluated against the bar in a fixed sequence to provide deterministic behavior.
89
+ Crucially, limit orders are processed after stop limit orders to ensure that limit orders created by stop limit orders are evaluated against the same bar.
90
+
91
+ parameters:
92
+ event:
93
+ Market bar used to trigger and price simulated fills.
94
+ """
95
+ self._process_market_orders(event)
96
+ self._process_stop_orders(event)
97
+ self._process_stop_limit_orders(event)
98
+ self._process_limit_orders(event)
99
+
100
+ def _process_market_orders(self, event: events.market.BarReceived) -> None:
101
+ """
102
+ Fill pending market orders for the bar symbol.
103
+
104
+ Market orders are filled at the bar open price on the next received bar for the matching symbol.
105
+
106
+ parameters:
107
+ event:
108
+ Market bar providing the simulated fill price and timestamps.
109
+ """
110
+ for order_id, order in list(self._pending_market_orders.items()):
111
+ if order.symbol != event.symbol:
112
+ continue
113
+
114
+ self._publish(
115
+ events.orders.FillEvent(
116
+ ts_event_ns=event.ts_event_ns,
117
+ ts_broker_ns=event.ts_event_ns,
118
+ associated_order_id=order.order_id,
119
+ symbol=order.symbol,
120
+ side=order.side,
121
+ quantity_filled=order.quantity,
122
+ fill_price=event.open,
123
+ commission=max(
124
+ order.quantity * self.commission_per_unit,
125
+ self.minimum_commission_per_order,
126
+ ),
127
+ )
128
+ )
129
+ del self._pending_market_orders[order_id]
130
+
131
+ def _process_stop_orders(self, event: events.market.BarReceived) -> None:
132
+ """
133
+ Evaluate and fill pending stop orders for the bar symbol.
134
+
135
+ Stop orders trigger when the bar crosses the stop level.
136
+ The fill price is modeled as the worse of the stop price and the bar open in the direction of the trade.
137
+
138
+ parameters:
139
+ event:
140
+ Market bar used to evaluate triggers and determine fill prices.
141
+ """
142
+ for order_id, order in list(self._pending_stop_orders.items()):
143
+ if order.symbol != event.symbol:
144
+ continue
145
+
146
+ # This is for mypy, it has already been validated on submission
147
+ assert order.stop_price is not None
148
+
149
+ triggered = False
150
+ match order.side:
151
+ case models.TradeSide.BUY:
152
+ triggered = event.high >= order.stop_price
153
+ case models.TradeSide.SELL:
154
+ triggered = event.low <= order.stop_price
155
+
156
+ if not triggered:
157
+ continue
158
+
159
+ fill_price = 0.0
160
+ match order.side:
161
+ case models.TradeSide.BUY:
162
+ fill_price = max(order.stop_price, event.open)
163
+ case models.TradeSide.SELL:
164
+ fill_price = min(order.stop_price, event.open)
165
+
166
+ self._publish(
167
+ events.orders.FillEvent(
168
+ ts_event_ns=event.ts_event_ns,
169
+ ts_broker_ns=event.ts_event_ns,
170
+ associated_order_id=order.order_id,
171
+ symbol=order.symbol,
172
+ side=order.side,
173
+ quantity_filled=order.quantity,
174
+ fill_price=fill_price,
175
+ commission=max(
176
+ order.quantity * self.commission_per_unit,
177
+ self.minimum_commission_per_order,
178
+ ),
179
+ )
180
+ )
181
+ del self._pending_stop_orders[order_id]
182
+
183
+ def _process_stop_limit_orders(self, event: events.market.BarReceived) -> None:
184
+ """
185
+ Evaluate pending stop-limit orders for the bar symbol.
186
+
187
+ Stop-limit orders trigger on stop conditions.
188
+ When triggered, they are converted into pending limit orders at the same identifier.
189
+
190
+ parameters:
191
+ event:
192
+ Market bar used to evaluate stop triggers.
193
+ """
194
+ for order_id, order in list(self._pending_stop_limit_orders.items()):
195
+ if order.symbol != event.symbol:
196
+ continue
197
+
198
+ assert order.stop_price is not None
199
+
200
+ triggered = False
201
+ match order.side:
202
+ case models.TradeSide.BUY:
203
+ triggered = event.high >= order.stop_price
204
+ case models.TradeSide.SELL:
205
+ triggered = event.low <= order.stop_price
206
+
207
+ if not triggered:
208
+ continue
209
+
210
+ limit_order = dataclasses.replace(order, order_type=models.OrderType.LIMIT)
211
+ self._pending_limit_orders[order_id] = limit_order
212
+ del self._pending_stop_limit_orders[order_id]
213
+
214
+ def _process_limit_orders(self, event: events.market.BarReceived) -> None:
215
+ """
216
+ Evaluate and fill pending limit orders for the bar symbol.
217
+
218
+ Limit orders trigger when the bar crosses the limit level.
219
+ The fill price is modeled as the better of the limit price and the bar open in the direction of the trade.
220
+
221
+ parameters:
222
+ event:
223
+ Market bar used to evaluate triggers and determine fill prices.
224
+ """
225
+ for order_id, order in list(self._pending_limit_orders.items()):
226
+ if order.symbol != event.symbol:
227
+ continue
228
+
229
+ assert order.limit_price is not None
230
+
231
+ triggered = False
232
+ match order.side:
233
+ case models.TradeSide.BUY:
234
+ triggered = event.low <= order.limit_price
235
+ case models.TradeSide.SELL:
236
+ triggered = event.high >= order.limit_price
237
+
238
+ if not triggered:
239
+ continue
240
+
241
+ fill_price = 0.0
242
+ match order.side:
243
+ case models.TradeSide.BUY:
244
+ fill_price = min(order.limit_price, event.open)
245
+ case models.TradeSide.SELL:
246
+ fill_price = max(order.limit_price, event.open)
247
+
248
+ self._publish(
249
+ events.orders.FillEvent(
250
+ ts_event_ns=event.ts_event_ns,
251
+ ts_broker_ns=event.ts_event_ns,
252
+ associated_order_id=order.order_id,
253
+ symbol=order.symbol,
254
+ side=order.side,
255
+ quantity_filled=order.quantity,
256
+ fill_price=fill_price,
257
+ commission=max(
258
+ order.quantity * self.commission_per_unit,
259
+ self.minimum_commission_per_order,
260
+ ),
261
+ )
262
+ )
263
+ del self._pending_limit_orders[order_id]
264
+
265
+ def _reject_if_invalid_submission(
266
+ self, event: events.requests.OrderSubmissionRequest
267
+ ) -> bool:
268
+ """
269
+ Validate an order submission request.
270
+
271
+ Invalid submissions are rejected immediately by publishing an `OrderRejected` response event.
272
+
273
+ parameters:
274
+ event:
275
+ Order submission request event to validate.
276
+
277
+ returns:
278
+ True if the submission is invalid and was rejected, otherwise False.
279
+ """
280
+ is_invalid = event.quantity <= 0
281
+
282
+ match event.order_type:
283
+ case models.OrderType.LIMIT:
284
+ is_invalid = (
285
+ is_invalid or event.limit_price is None or event.limit_price <= 0
286
+ )
287
+ case models.OrderType.STOP:
288
+ is_invalid = (
289
+ is_invalid or event.stop_price is None or event.stop_price <= 0
290
+ )
291
+ case models.OrderType.STOP_LIMIT:
292
+ is_invalid = is_invalid or (
293
+ event.limit_price is None
294
+ or event.limit_price <= 0
295
+ or event.stop_price is None
296
+ or event.stop_price <= 0
297
+ )
298
+
299
+ if is_invalid:
300
+ self._publish(
301
+ events.responses.OrderRejected(
302
+ ts_event_ns=event.ts_event_ns,
303
+ ts_broker_ns=event.ts_event_ns,
304
+ associated_order_id=event.system_order_id,
305
+ rejection_reason=models.OrderRejectionReason.UNKNOWN,
306
+ rejection_message="Unknown",
307
+ )
308
+ )
309
+
310
+ return is_invalid
311
+
312
+ def _on_submit_order(self, event: events.requests.OrderSubmissionRequest) -> None:
313
+ """
314
+ Handle an order submission request.
315
+
316
+ Valid orders are stored as pending broker-side state and acknowledged via an `OrderAccepted` response event.
317
+
318
+ parameters:
319
+ event:
320
+ Order submission request event.
321
+ """
322
+ if self._reject_if_invalid_submission(event):
323
+ return
324
+
325
+ order = _PendingOrder(
326
+ order_id=event.system_order_id,
327
+ symbol=event.symbol,
328
+ order_type=event.order_type,
329
+ side=event.side,
330
+ quantity=event.quantity,
331
+ limit_price=event.limit_price,
332
+ stop_price=event.stop_price,
333
+ )
334
+
335
+ match order.order_type:
336
+ case models.OrderType.MARKET:
337
+ self._pending_market_orders[order.order_id] = order
338
+ case models.OrderType.LIMIT:
339
+ self._pending_limit_orders[order.order_id] = order
340
+ case models.OrderType.STOP:
341
+ self._pending_stop_orders[order.order_id] = order
342
+ case models.OrderType.STOP_LIMIT:
343
+ self._pending_stop_limit_orders[order.order_id] = order
344
+
345
+ self._publish(
346
+ events.responses.OrderAccepted(
347
+ ts_event_ns=event.ts_event_ns,
348
+ ts_broker_ns=event.ts_event_ns,
349
+ associated_order_id=order.order_id,
350
+ )
351
+ )
352
+
353
+ def _on_cancel_order(self, event: events.requests.OrderCancellationRequest) -> None:
354
+ """
355
+ Handle an order cancellation request.
356
+
357
+ If the referenced order is pending, it is removed and acknowledged via `CancellationAccepted`.
358
+ Otherwise, `CancellationRejected` is published.
359
+
360
+ parameters:
361
+ event:
362
+ Order cancellation request event.
363
+ """
364
+ order_id = event.system_order_id
365
+
366
+ removed = False
367
+ for pending_orders in (
368
+ self._pending_market_orders,
369
+ self._pending_limit_orders,
370
+ self._pending_stop_orders,
371
+ self._pending_stop_limit_orders,
372
+ ):
373
+ if order_id in pending_orders:
374
+ del pending_orders[order_id]
375
+ removed = True
376
+ break
377
+
378
+ if removed:
379
+ self._publish(
380
+ events.responses.CancellationAccepted(
381
+ ts_event_ns=event.ts_event_ns,
382
+ ts_broker_ns=event.ts_event_ns,
383
+ associated_order_id=order_id,
384
+ )
385
+ )
386
+ else:
387
+ self._publish(
388
+ events.responses.CancellationRejected(
389
+ ts_event_ns=event.ts_event_ns,
390
+ ts_broker_ns=event.ts_event_ns,
391
+ associated_order_id=order_id,
392
+ rejection_reason=models.CancellationRejectionReason.UNKNOWN,
393
+ rejection_message="Unknown",
394
+ )
395
+ )
396
+
397
+ def _reject_if_invalid_modification(
398
+ self, event: events.requests.OrderModificationRequest
399
+ ) -> bool:
400
+ """
401
+ Validate an order modification request.
402
+
403
+ Invalid modifications are rejected immediately by publishing a `ModificationRejected` response event.
404
+
405
+ parameters:
406
+ event:
407
+ Order modification request event to validate.
408
+
409
+ returns:
410
+ True if the modification is invalid and was rejected, otherwise False.
411
+ """
412
+ is_invalid = (
413
+ (event.quantity is not None and event.quantity <= 0)
414
+ or (event.limit_price is not None and event.limit_price <= 0)
415
+ or (event.stop_price is not None and event.stop_price <= 0)
416
+ )
417
+
418
+ if is_invalid:
419
+ self._publish(
420
+ events.responses.ModificationRejected(
421
+ ts_event_ns=event.ts_event_ns,
422
+ ts_broker_ns=event.ts_event_ns,
423
+ associated_order_id=event.system_order_id,
424
+ rejection_reason=models.ModificationRejectionReason.UNKNOWN,
425
+ rejection_message="Unknown",
426
+ )
427
+ )
428
+
429
+ return is_invalid
430
+
431
+ def _on_modify_order(self, event: events.requests.OrderModificationRequest) -> None:
432
+ """
433
+ Handle an order modification request.
434
+
435
+ If the referenced order is pending, its fields are updated and acknowledged via `ModificationAccepted`.
436
+ Otherwise, `ModificationRejected` is published.
437
+
438
+ parameters:
439
+ event:
440
+ Order modification request event.
441
+ """
442
+ if self._reject_if_invalid_modification(event):
443
+ return
444
+
445
+ order_id = event.system_order_id
446
+
447
+ for pending_orders in (
448
+ self._pending_market_orders,
449
+ self._pending_limit_orders,
450
+ self._pending_stop_orders,
451
+ self._pending_stop_limit_orders,
452
+ ):
453
+ if order_id in pending_orders:
454
+ order = pending_orders[order_id]
455
+
456
+ new_quantity = (
457
+ event.quantity if event.quantity is not None else order.quantity
458
+ )
459
+ new_limit_price = (
460
+ event.limit_price
461
+ if event.limit_price is not None
462
+ else order.limit_price
463
+ )
464
+ new_stop_price = (
465
+ event.stop_price
466
+ if event.stop_price is not None
467
+ else order.stop_price
468
+ )
469
+
470
+ pending_orders[order_id] = dataclasses.replace(
471
+ order,
472
+ quantity=new_quantity,
473
+ limit_price=new_limit_price,
474
+ stop_price=new_stop_price,
475
+ )
476
+
477
+ self._publish(
478
+ events.responses.ModificationAccepted(
479
+ ts_event_ns=event.ts_event_ns,
480
+ ts_broker_ns=event.ts_event_ns,
481
+ associated_order_id=order_id,
482
+ )
483
+ )
484
+ return
485
+
486
+ self._publish(
487
+ events.responses.ModificationRejected(
488
+ ts_event_ns=event.ts_event_ns,
489
+ ts_broker_ns=event.ts_event_ns,
490
+ associated_order_id=order_id,
491
+ rejection_reason=models.ModificationRejectionReason.UNKNOWN,
492
+ rejection_message="Unknown",
493
+ )
494
+ )
@@ -9,17 +9,17 @@ from onesecondtrader import events
9
9
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
10
10
  class OrderBase(events.EventBase):
11
11
  """
12
- Base class for broker-originated order events.
12
+ Base class for brokers-originated order events.
13
13
 
14
- Order events are broker-originated facts about the state or execution of an order.
14
+ Order events are brokers-originated facts about the state or execution of an order.
15
15
  Each order event is correlated to a system order identifier via `associated_order_id`.
16
16
 
17
17
  | Field | Type | Semantics |
18
18
  |-----------------------|-----------------|---------------------------------------------------------------------------------------|
19
19
  | `ts_event_ns` | `int` | Time at which the response event was observed by the system, as UTC epoch nanoseconds.|
20
20
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
21
- | `ts_broker_ns` | `int` | Time reported by the broker for the response, as UTC epoch nanoseconds. |
22
- | `associated_order_id` | `uuid.UUID`. | Identifier of the order associated with the broker response. |
21
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the response, as UTC epoch nanoseconds. |
22
+ | `associated_order_id` | `uuid.UUID`. | Identifier of the order associated with the brokers response. |
23
23
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the order, if reported. |
24
24
  | `symbol` | `str` | Identifier of the traded instrument. |
25
25
  """
@@ -8,13 +8,13 @@ from onesecondtrader.events.orders.base import OrderBase
8
8
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
9
9
  class OrderExpired(OrderBase):
10
10
  """
11
- Event indicating that the order is no longer active at the venue due to expiration according to broker- or venue-specific rules (e.g. time-in-force constraints).
11
+ Event indicating that the order is no longer active at the venue due to expiration according to brokers- or venue-specific rules (e.g. time-in-force constraints).
12
12
 
13
13
  | Field | Type | Semantics |
14
14
  |-----------------------|-----------------|------------------------------------------------------------------------------------|
15
15
  | `ts_event_ns` | `int` | Time at which the expiration was observed by the system, as UTC epoch nanoseconds. |
16
16
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
17
- | `ts_broker_ns` | `int` | Time reported by the broker for the expiration, as UTC epoch nanoseconds. |
17
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the expiration, as UTC epoch nanoseconds. |
18
18
  | `associated_order_id` | `uuid.UUID` | Identifier of the expired order. |
19
19
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the expired order, if reported. |
20
20
  | `symbol` | `str` | Identifier of the traded instrument. |
@@ -19,7 +19,7 @@ class FillEvent(OrderBase):
19
19
  |-----------------------|---------------------|---------------------------------------------------------------------------------|
20
20
  | `ts_event_ns` | `int` | Time at which the fill was observed by the system, as UTC epoch nanoseconds. |
21
21
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
22
- | `ts_broker_ns` | `int` | Time reported by the broker for the fill, as UTC epoch nanoseconds. |
22
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the fill, as UTC epoch nanoseconds. |
23
23
  | `associated_order_id` | `uuid.UUID` | Identifier of the order associated with the fill. |
24
24
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the order associated with the fill, if available. |
25
25
  | `symbol` | `str` | Identifier of the traded instrument. |
@@ -11,7 +11,7 @@ class RequestBase(events.EventBase):
11
11
  """
12
12
  Base class for request events.
13
13
 
14
- This class defines attributes common to all requests issued to a broker.
14
+ This class defines attributes common to all requests issued to a brokers.
15
15
 
16
16
  | Field | Type | Semantics |
17
17
  |-------------------|-------------|----------------------------------------------------------------------------|
@@ -10,7 +10,7 @@ from onesecondtrader.events.requests.base import RequestBase
10
10
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
11
11
  class OrderSubmissionRequest(RequestBase):
12
12
  """
13
- Event representing a request to submit a new order to a broker.
13
+ Event representing a request to submit a new order to a brokers.
14
14
 
15
15
  The `system_order_id` is a unique identifier assigned by the system to the order submission request by default at object creation.
16
16
 
@@ -10,16 +10,16 @@ from onesecondtrader import events
10
10
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
11
11
  class ResponseBase(events.EventBase):
12
12
  """
13
- Base class for broker response events.
13
+ Base class for brokers response events.
14
14
 
15
- This class defines attributes common to all responses received from a broker in reaction to previously issued requests.
15
+ This class defines attributes common to all responses received from a brokers in reaction to previously issued requests.
16
16
 
17
17
  | Field | Type | Semantics |
18
18
  |-----------------------|-------------|---------------------------------------------------------------------------------------|
19
19
  | `ts_event_ns` | `int` | Time at which the response event was observed by the system, as UTC epoch nanoseconds.|
20
20
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
21
- | `ts_broker_ns` | `int` | Time reported by the broker for the response, as UTC epoch nanoseconds. |
22
- | `associated_order_id` | `uuid.UUID` | Identifier of the order associated with the broker response. |
21
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the response, as UTC epoch nanoseconds. |
22
+ | `associated_order_id` | `uuid.UUID` | Identifier of the order associated with the brokers response. |
23
23
  """
24
24
 
25
25
  ts_broker_ns: int
@@ -9,13 +9,13 @@ from onesecondtrader.events.responses.base import ResponseBase
9
9
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
10
10
  class CancellationAccepted(ResponseBase):
11
11
  """
12
- Event indicating that the order cancellation has been acknowledged by the broker and the order is no longer active at the execution venue.
12
+ Event indicating that the order cancellation has been acknowledged by the brokers and the order is no longer active at the execution venue.
13
13
 
14
14
  | Field | Type | Semantics |
15
15
  |-----------------------|-----------------|----------------------------------------------------------------------------------------|
16
16
  | `ts_event_ns` | `int` | Time at which the cancellation was observed by the system, as UTC epoch nanoseconds. |
17
17
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
18
- | `ts_broker_ns` | `int` | Time reported by the broker for the cancellation, as UTC epoch nanoseconds. |
18
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the cancellation, as UTC epoch nanoseconds. |
19
19
  | `associated_order_id` | `uuid.UUID` | Identifier of the cancelled order. |
20
20
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the cancelled order, if reported. |
21
21
  """
@@ -26,16 +26,16 @@ class CancellationAccepted(ResponseBase):
26
26
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
27
27
  class CancellationRejected(ResponseBase):
28
28
  """
29
- Event indicating that the order cancellation has been rejected by the broker.
29
+ Event indicating that the order cancellation has been rejected by the brokers.
30
30
 
31
31
  | Field | Type | Semantics |
32
32
  |-----------------------|--------------------------------------|------------------------------------------------------------------------------------|
33
33
  | `ts_event_ns` | `int` | Time at which the rejection was observed by the system, as UTC epoch nanoseconds. |
34
34
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
35
- | `ts_broker_ns` | `int` | Time reported by the broker for the rejection, as UTC epoch nanoseconds. |
35
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the rejection, as UTC epoch nanoseconds. |
36
36
  | `associated_order_id` | `uuid.UUID` | Identifier of the order associated with the rejected cancellation. |
37
37
  | `rejection_reason` | `models.CancellationRejectionReason` | Canonical classification of the cancellation rejection cause. |
38
- | `rejection_message` | `str` | Human-readable explanation provided by the broker. |
38
+ | `rejection_message` | `str` | Human-readable explanation provided by the brokers. |
39
39
  """
40
40
 
41
41
  rejection_reason: models.CancellationRejectionReason
@@ -10,13 +10,13 @@ from onesecondtrader.events.responses.base import ResponseBase
10
10
  class ModificationAccepted(ResponseBase):
11
11
  """
12
12
  Event indicating that the requested modification has been acknowledged by
13
- the broker and that the updated order parameters are active at the execution venue.
13
+ the brokers and that the updated order parameters are active at the execution venue.
14
14
 
15
15
  | Field | Type | Semantics |
16
16
  |-----------------------|-----------------|----------------------------------------------------------------------------------------|
17
17
  | `ts_event_ns` | `int` | Time at which the acceptance was observed by the system, as UTC epoch nanoseconds. |
18
18
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
19
- | `ts_broker_ns` | `int` | Time reported by the broker for the modification acceptance, as UTC epoch nanoseconds. |
19
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the modification acceptance, as UTC epoch nanoseconds. |
20
20
  | `associated_order_id` | `uuid.UUID` | Identifier of the modified order. |
21
21
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the order after modification, if reported. |
22
22
  """
@@ -27,16 +27,16 @@ class ModificationAccepted(ResponseBase):
27
27
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
28
28
  class ModificationRejected(ResponseBase):
29
29
  """
30
- Event indicating that the requested modification has been rejected by the broker.
30
+ Event indicating that the requested modification has been rejected by the brokers.
31
31
 
32
32
  | Field | Type | Semantics |
33
33
  |-----------------------|--------------------------------------|------------------------------------------------------------------------------------|
34
34
  | `ts_event_ns` | `int` | Time at which the rejection was observed by the system, as UTC epoch nanoseconds. |
35
35
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
36
- | `ts_broker_ns` | `int` | Time reported by the broker for the rejection, as UTC epoch nanoseconds. |
36
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the rejection, as UTC epoch nanoseconds. |
37
37
  | `associated_order_id` | `uuid.UUID` | Identifier of the order associated with the rejected modification. |
38
38
  | `rejection_reason` | `models.ModificationRejectionReason` | Canonical classification of the modification rejection cause. |
39
- | `rejection_message` | `str` | Human-readable explanation provided by the broker. |
39
+ | `rejection_message` | `str` | Human-readable explanation provided by the brokers. |
40
40
  """
41
41
 
42
42
  rejection_reason: models.ModificationRejectionReason
@@ -9,13 +9,13 @@ from onesecondtrader.events.responses.base import ResponseBase
9
9
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
10
10
  class OrderAccepted(ResponseBase):
11
11
  """
12
- Event indicating that the order has been accepted by the broker and is active at the execution venue.
12
+ Event indicating that the order has been accepted by the brokers and is active at the execution venue.
13
13
 
14
14
  | Field | Type | Semantics |
15
15
  |-----------------------|-----------------|------------------------------------------------------------------------------------|
16
16
  | `ts_event_ns` | `int` | Time at which the acceptance was observed by the system, as UTC epoch nanoseconds. |
17
17
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
18
- | `ts_broker_ns` | `int` | Time reported by the broker for the acceptance, as UTC epoch nanoseconds. |
18
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the acceptance, as UTC epoch nanoseconds. |
19
19
  | `associated_order_id` | `uuid.UUID` | Identifier of the accepted order. |
20
20
  | `broker_order_id` | `str` or `None` | Broker-assigned identifier of the accepted order. |
21
21
  """
@@ -26,16 +26,16 @@ class OrderAccepted(ResponseBase):
26
26
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
27
27
  class OrderRejected(ResponseBase):
28
28
  """
29
- Event indicating that the order has been rejected by the broker.
29
+ Event indicating that the order has been rejected by the brokers.
30
30
 
31
31
  | Field | Type | Semantics |
32
32
  |-----------------------|-------------------------------|------------------------------------------------------------------------------------|
33
33
  | `ts_event_ns` | `int` | Time at which the rejection was observed by the system, as UTC epoch nanoseconds. |
34
34
  | `ts_created_ns` | `int` | Time at which the event object was created, as UTC epoch nanoseconds. |
35
- | `ts_broker_ns` | `int` | Time reported by the broker for the rejection, as UTC epoch nanoseconds. |
35
+ | `ts_broker_ns` | `int` | Time reported by the brokers for the rejection, as UTC epoch nanoseconds. |
36
36
  | `associated_order_id` | `uuid.UUID` | Identifier of the rejected order. |
37
37
  | `rejection_reason` | `models.OrderRejectionReason` | Canonical classification of the rejection cause. |
38
- | `rejection_message` | `str` | Human-readable explanation provided by the broker. |
38
+ | `rejection_message` | `str` | Human-readable explanation provided by the brokers. |
39
39
  """
40
40
 
41
41
  rejection_reason: models.OrderRejectionReason
@@ -0,0 +1,11 @@
1
+ """
2
+ Provides the infrastructure for event-based communication between system components.
3
+ """
4
+
5
+ from .eventbus import EventBus
6
+ from .subscriber import Subscriber
7
+
8
+ __all__ = [
9
+ "EventBus",
10
+ "Subscriber",
11
+ ]
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import collections
4
+ import threading
5
+ import typing
6
+
7
+ from onesecondtrader import events
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from .subscriber import Subscriber
11
+
12
+
13
+ class EventBus:
14
+ """
15
+ Event dispatch mechanism for propagating event objects to subscribers.
16
+
17
+ The event bus maintains subscriptions between subscribers and concrete event types.
18
+ Events published to the bus are synchronously delivered to all subscribers registered for the exact event type.
19
+
20
+ Subscription management and event publication are thread-safe.
21
+ Event delivery itself occurs outside the internal lock.
22
+ """
23
+
24
+ def __init__(self) -> None:
25
+ """
26
+ Initialize an empty event bus.
27
+
28
+ The bus starts with no registered subscribers and no active subscriptions.
29
+ """
30
+ self._per_event_subscriptions: collections.defaultdict[
31
+ type[events.EventBase], set[Subscriber]
32
+ ] = collections.defaultdict(set)
33
+ self._subscribers: set[Subscriber] = set()
34
+ self._lock: threading.Lock = threading.Lock()
35
+
36
+ def subscribe(
37
+ self,
38
+ subscriber: Subscriber,
39
+ event_type: type[events.EventBase],
40
+ ) -> None:
41
+ """
42
+ Register a subscriber for a specific event type.
43
+
44
+ The subscriber will receive all future events whose concrete type matches `event_type`.
45
+
46
+ Parameters:
47
+ subscriber:
48
+ Object receiving published events.
49
+ event_type:
50
+ Concrete event class the subscriber is interested in.
51
+ """
52
+ with self._lock:
53
+ self._subscribers.add(subscriber)
54
+ self._per_event_subscriptions[event_type].add(subscriber)
55
+
56
+ def unsubscribe(self, subscriber: Subscriber) -> None:
57
+ """
58
+ Remove a subscriber from all event subscriptions.
59
+
60
+ After unsubscription, the subscriber will no longer receive any events published on this bus.
61
+
62
+ Parameters:
63
+ subscriber:
64
+ Subscriber to remove.
65
+ """
66
+ with self._lock:
67
+ for set_of_event_subscribers in self._per_event_subscriptions.values():
68
+ set_of_event_subscribers.discard(subscriber)
69
+ self._subscribers.discard(subscriber)
70
+
71
+ def publish(self, event: events.EventBase) -> None:
72
+ """
73
+ Publish an event to all subscribed listeners.
74
+
75
+ Subscribers are matched strictly by the concrete type of the event.
76
+ Parent classes and inheritance relationships are not considered.
77
+
78
+ Parameters:
79
+ event:
80
+ Event instance to dispatch.
81
+ """
82
+ with self._lock:
83
+ subscribers = self._per_event_subscriptions[type(event)].copy()
84
+ for subscriber in subscribers:
85
+ subscriber.receive(event)
86
+
87
+ def wait_until_system_idle(self) -> None:
88
+ """
89
+ Block until all subscribers report an idle state.
90
+
91
+ This method delegates to each subscriber's `wait_until_idle` method and returns only after all subscribers have completed any pending work.
92
+ """
93
+ with self._lock:
94
+ subscribers = self._subscribers.copy()
95
+ for subscriber in subscribers:
96
+ subscriber.wait_until_idle()
@@ -0,0 +1,153 @@
1
+ import abc
2
+ import queue
3
+ import threading
4
+
5
+ from onesecondtrader import events, messaging
6
+
7
+
8
+ class Subscriber(abc.ABC):
9
+ """
10
+ Abstract base class for event bus subscribers.
11
+
12
+ A subscriber receives events from an event bus and processes them asynchronously in a dedicated worker thread.
13
+ Incoming events are queued and handled sequentially.
14
+
15
+ Subclasses implement `_on_event` to define event-specific behavior.
16
+ """
17
+
18
+ def __init__(self, event_bus: messaging.EventBus) -> None:
19
+ """
20
+ Initialize the subscriber and start its event-processing thread.
21
+
22
+ Parameters:
23
+ event_bus:
24
+ Event bus used for subscribing to and publishing events.
25
+ """
26
+ self._event_bus = event_bus
27
+ self._queue: queue.Queue[events.EventBase | None] = queue.Queue()
28
+
29
+ self._running: threading.Event = threading.Event()
30
+ self._running.set()
31
+
32
+ self._thread = threading.Thread(
33
+ target=self._event_loop, name=self.__class__.__name__
34
+ )
35
+ self._thread.start()
36
+
37
+ def receive(self, event: events.EventBase) -> None:
38
+ """
39
+ Receive an event from the event bus.
40
+
41
+ The event is enqueued for asynchronous processing if the subscriber is running.
42
+
43
+ Parameters:
44
+ event:
45
+ Event instance delivered by the event bus.
46
+ """
47
+ if self._running.is_set():
48
+ self._queue.put(event)
49
+
50
+ def wait_until_idle(self) -> None:
51
+ """
52
+ Block until all queued events have been processed.
53
+
54
+ If the subscriber is not running, this method returns immediately.
55
+ """
56
+ if not self._running.is_set():
57
+ return
58
+
59
+ self._queue.join()
60
+
61
+ def shutdown(self) -> None:
62
+ """
63
+ Shut down the subscriber and stop event processing.
64
+
65
+ The subscriber is unsubscribed from the event bus, its worker thread is signaled to terminate, and all pending events are processed before shutdown completes.
66
+ """
67
+ if not self._running.is_set():
68
+ return
69
+
70
+ self._event_bus.unsubscribe(self)
71
+ self._running.clear()
72
+ self._queue.put(None)
73
+
74
+ if threading.current_thread() is not self._thread:
75
+ self._thread.join()
76
+
77
+ def _subscribe(self, *event_types: type[events.EventBase]) -> None:
78
+ """
79
+ Subscribe this subscriber to one or more event types.
80
+
81
+ Parameters:
82
+ *event_types:
83
+ Concrete event classes to subscribe to.
84
+ """
85
+ for event_type in event_types:
86
+ self._event_bus.subscribe(self, event_type)
87
+
88
+ def _publish(self, event: events.EventBase) -> None:
89
+ """
90
+ Publish an event to the event bus.
91
+
92
+ Parameters:
93
+ event:
94
+ Event instance to publish.
95
+ """
96
+ self._event_bus.publish(event)
97
+
98
+ def _event_loop(self) -> None:
99
+ """
100
+ Internal worker loop for processing queued events.
101
+
102
+ This method runs in a dedicated thread and should not be called directly.
103
+ """
104
+ while True:
105
+ event = self._queue.get()
106
+
107
+ if event is None:
108
+ self._queue.task_done()
109
+ break
110
+
111
+ try:
112
+ self._on_event(event)
113
+ except Exception as exc:
114
+ self._on_exception(exc)
115
+ finally:
116
+ self._queue.task_done()
117
+
118
+ self._cleanup()
119
+
120
+ def _on_exception(self, exc: Exception) -> None:
121
+ """
122
+ Handle an exception raised during event processing.
123
+
124
+ Subclasses may override this method to implement logging or recovery behavior.
125
+ The default implementation ignores the exception.
126
+
127
+ Parameters:
128
+ exc:
129
+ Exception raised while processing an event.
130
+ """
131
+ pass
132
+
133
+ def _cleanup(self) -> None:
134
+ """
135
+ Perform cleanup after the event loop terminates.
136
+
137
+ Subclasses may override this method to release resources or emit shutdown notifications.
138
+ """
139
+ pass
140
+
141
+ @abc.abstractmethod
142
+ def _on_event(self, event: events.EventBase) -> None:
143
+ """
144
+ Handle a single event.
145
+
146
+ This method is invoked sequentially for each event received by the subscriber.
147
+ Implementations must not block indefinitely, as `wait_until_idle` relies on timely completion.
148
+
149
+ Parameters:
150
+ event:
151
+ Event instance to handle.
152
+ """
153
+ ...
@@ -8,7 +8,7 @@ class OrderRejectionReason(enum.Enum):
8
8
  Enumeration of canonical order rejection reasons.
9
9
 
10
10
  This enumeration defines the system-level classification of order rejection causes.
11
- It provides a stable, broker-agnostic taxonomy for programmatic handling of rejected orders.
11
+ It provides a stable, brokers-agnostic taxonomy for programmatic handling of rejected orders.
12
12
 
13
13
  | Value | Semantics |
14
14
  |-----------|---------------------------------------------------------------------------|
@@ -22,8 +22,8 @@ class ModificationRejectionReason(enum.Enum):
22
22
  """
23
23
  Enumeration of canonical order modification rejection reasons.
24
24
 
25
- This enumeration defines the system-level classification of reasons for which an order modification request may be rejected by a broker.
26
- It provides a stable, broker-agnostic taxonomy intended for programmatic handling and observability of modification rejections.
25
+ This enumeration defines the system-level classification of reasons for which an order modification request may be rejected by a brokers.
26
+ It provides a stable, brokers-agnostic taxonomy intended for programmatic handling and observability of modification rejections.
27
27
 
28
28
  | Value | Semantics |
29
29
  |-----------|----------------------------------------------------------------------------------|
@@ -37,8 +37,8 @@ class CancellationRejectionReason(enum.Enum):
37
37
  """
38
38
  Enumeration of canonical order cancellation rejection reasons.
39
39
 
40
- This enumeration defines the system-level classification of reasons for which an order cancellation request may be rejected by a broker.
41
- It provides a stable, broker-agnostic taxonomy intended for programmatic handling and observability of cancellation rejections.
40
+ This enumeration defines the system-level classification of reasons for which an order cancellation request may be rejected by a brokers.
41
+ It provides a stable, brokers-agnostic taxonomy intended for programmatic handling and observability of cancellation rejections.
42
42
 
43
43
  | Value | Semantics |
44
44
  |-----------|----------------------------------------------------------------------------------|