onesecondtrader 0.22.0__tar.gz → 0.33.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 (32) hide show
  1. {onesecondtrader-0.22.0 → onesecondtrader-0.33.0}/PKG-INFO +1 -1
  2. {onesecondtrader-0.22.0 → onesecondtrader-0.33.0}/pyproject.toml +1 -1
  3. onesecondtrader-0.33.0/src/onesecondtrader/__init__.py +49 -0
  4. onesecondtrader-0.33.0/src/onesecondtrader/brokers/__init__.py +7 -0
  5. onesecondtrader-0.33.0/src/onesecondtrader/brokers/base.py +39 -0
  6. onesecondtrader-0.33.0/src/onesecondtrader/brokers/simulated.py +346 -0
  7. onesecondtrader-0.33.0/src/onesecondtrader/events/__init__.py +33 -0
  8. onesecondtrader-0.33.0/src/onesecondtrader/events/bases.py +29 -0
  9. onesecondtrader-0.33.0/src/onesecondtrader/events/market.py +22 -0
  10. onesecondtrader-0.33.0/src/onesecondtrader/events/requests.py +31 -0
  11. onesecondtrader-0.33.0/src/onesecondtrader/events/responses.py +54 -0
  12. onesecondtrader-0.33.0/src/onesecondtrader/indicators/__init__.py +13 -0
  13. onesecondtrader-0.33.0/src/onesecondtrader/indicators/averages.py +56 -0
  14. onesecondtrader-0.33.0/src/onesecondtrader/indicators/bar.py +47 -0
  15. onesecondtrader-0.33.0/src/onesecondtrader/indicators/base.py +60 -0
  16. onesecondtrader-0.33.0/src/onesecondtrader/messaging/__init__.py +7 -0
  17. onesecondtrader-0.33.0/src/onesecondtrader/messaging/eventbus.py +47 -0
  18. onesecondtrader-0.33.0/src/onesecondtrader/messaging/subscriber.py +69 -0
  19. onesecondtrader-0.33.0/src/onesecondtrader/models/__init__.py +12 -0
  20. onesecondtrader-0.33.0/src/onesecondtrader/models/data.py +18 -0
  21. onesecondtrader-0.33.0/src/onesecondtrader/models/orders.py +15 -0
  22. onesecondtrader-0.33.0/src/onesecondtrader/models/records.py +32 -0
  23. onesecondtrader-0.33.0/src/onesecondtrader/strategies/__init__.py +7 -0
  24. onesecondtrader-0.33.0/src/onesecondtrader/strategies/base.py +318 -0
  25. onesecondtrader-0.33.0/src/onesecondtrader/strategies/sma_crossover.py +35 -0
  26. onesecondtrader-0.22.0/src/onesecondtrader/__init__.py +0 -0
  27. onesecondtrader-0.22.0/src/onesecondtrader/brokers.py +0 -92
  28. onesecondtrader-0.22.0/src/onesecondtrader/core.py +0 -260
  29. onesecondtrader-0.22.0/src/onesecondtrader/datafeeds.py +0 -173
  30. onesecondtrader-0.22.0/src/onesecondtrader/indicators.py +0 -106
  31. {onesecondtrader-0.22.0 → onesecondtrader-0.33.0}/LICENSE +0 -0
  32. {onesecondtrader-0.22.0 → onesecondtrader-0.33.0}/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onesecondtrader
3
- Version: 0.22.0
3
+ Version: 0.33.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.22.0"
3
+ version = "0.33.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,49 @@
1
+ __all__ = [
2
+ "BarPeriod",
3
+ "BarProcessed",
4
+ "BarReceived",
5
+ "BrokerBase",
6
+ "Close",
7
+ "FillRecord",
8
+ "High",
9
+ "Indicator",
10
+ "InputSource",
11
+ "Low",
12
+ "Open",
13
+ "OrderFilled",
14
+ "OrderRecord",
15
+ "OrderSide",
16
+ "OrderSubmission",
17
+ "OrderType",
18
+ "SimpleMovingAverage",
19
+ "SimulatedBroker",
20
+ "SMACrossover",
21
+ "StrategyBase",
22
+ "Volume",
23
+ ]
24
+
25
+ from onesecondtrader.brokers import BrokerBase, SimulatedBroker
26
+ from onesecondtrader.events import (
27
+ BarProcessed,
28
+ BarReceived,
29
+ OrderFilled,
30
+ OrderSubmission,
31
+ )
32
+ from onesecondtrader.indicators import (
33
+ Close,
34
+ High,
35
+ Indicator,
36
+ Low,
37
+ Open,
38
+ SimpleMovingAverage,
39
+ Volume,
40
+ )
41
+ from onesecondtrader.models import (
42
+ BarPeriod,
43
+ FillRecord,
44
+ InputSource,
45
+ OrderRecord,
46
+ OrderSide,
47
+ OrderType,
48
+ )
49
+ from onesecondtrader.strategies import SMACrossover, StrategyBase
@@ -0,0 +1,7 @@
1
+ __all__ = [
2
+ "BrokerBase",
3
+ "SimulatedBroker",
4
+ ]
5
+
6
+ from .base import BrokerBase
7
+ from .simulated import SimulatedBroker
@@ -0,0 +1,39 @@
1
+ import abc
2
+
3
+ from onesecondtrader import events, messaging
4
+
5
+
6
+ class BrokerBase(messaging.Subscriber):
7
+ def __init__(self, event_bus: messaging.EventBus) -> None:
8
+ super().__init__(event_bus)
9
+ self._subscribe(
10
+ events.requests.OrderSubmission,
11
+ events.requests.OrderCancellation,
12
+ events.requests.OrderModification,
13
+ )
14
+
15
+ def _on_event(self, event: events.bases.EventBase) -> None:
16
+ match event:
17
+ case events.requests.OrderSubmission() as submit_order:
18
+ self._on_submit_order(submit_order)
19
+ case events.requests.OrderCancellation() as cancel_order:
20
+ self._on_cancel_order(cancel_order)
21
+ case events.requests.OrderModification() as modify_order:
22
+ self._on_modify_order(modify_order)
23
+ case _:
24
+ return
25
+
26
+ @abc.abstractmethod
27
+ def _on_submit_order(self, event: events.requests.OrderSubmission) -> None:
28
+ pass
29
+
30
+ @abc.abstractmethod
31
+ def _on_cancel_order(self, event: events.requests.OrderCancellation) -> None:
32
+ pass
33
+
34
+ @abc.abstractmethod
35
+ def _on_modify_order(self, event: events.requests.OrderModification) -> None:
36
+ pass
37
+
38
+ def _respond(self, response_event: events.bases.BrokerResponseEvent) -> None:
39
+ self._publish(response_event)
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import uuid
5
+
6
+ from onesecondtrader import events, messaging, models
7
+ from .base import BrokerBase
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class _PendingOrder:
12
+ # Order state tracked by the broker, distinct from the OrderSubmission event
13
+ order_id: uuid.UUID
14
+ symbol: str
15
+ order_type: models.OrderType
16
+ side: models.OrderSide
17
+ quantity: float
18
+ limit_price: float | None = None
19
+ stop_price: float | None = None
20
+
21
+
22
+ class SimulatedBroker(BrokerBase):
23
+ commission_per_unit: float = 0.0
24
+ minimum_commission_per_order: float = 0.0
25
+
26
+ def __init__(self, event_bus: messaging.EventBus) -> None:
27
+ self._pending_market_orders: dict[uuid.UUID, _PendingOrder] = {}
28
+ self._pending_limit_orders: dict[uuid.UUID, _PendingOrder] = {}
29
+ self._pending_stop_orders: dict[uuid.UUID, _PendingOrder] = {}
30
+ self._pending_stop_limit_orders: dict[uuid.UUID, _PendingOrder] = {}
31
+
32
+ super().__init__(event_bus)
33
+ self._subscribe(events.BarReceived)
34
+
35
+ def _on_event(self, event: events.EventBase) -> None:
36
+ match event:
37
+ case events.BarReceived() as bar:
38
+ self._on_bar(bar)
39
+ case _:
40
+ super()._on_event(event)
41
+
42
+ def _on_bar(self, event: events.BarReceived) -> None:
43
+ self._process_market_orders(event)
44
+ self._process_stop_orders(event)
45
+ self._process_stop_limit_orders(event)
46
+ self._process_limit_orders(event)
47
+
48
+ def _process_market_orders(self, event: events.BarReceived) -> None:
49
+ for order_id, order in list(self._pending_market_orders.items()):
50
+ if order.symbol != event.symbol:
51
+ continue
52
+
53
+ self._publish(
54
+ events.OrderFilled(
55
+ ts_event=event.ts_event,
56
+ ts_broker=event.ts_event,
57
+ associated_order_id=order.order_id,
58
+ symbol=order.symbol,
59
+ side=order.side,
60
+ quantity_filled=order.quantity,
61
+ fill_price=event.open,
62
+ commission=max(
63
+ order.quantity * self.commission_per_unit,
64
+ self.minimum_commission_per_order,
65
+ ),
66
+ )
67
+ )
68
+ del self._pending_market_orders[order_id]
69
+
70
+ def _process_stop_orders(self, event: events.BarReceived) -> None:
71
+ for order_id, order in list(self._pending_stop_orders.items()):
72
+ if order.symbol != event.symbol:
73
+ continue
74
+
75
+ # This is for mypy, it has already been validated on submission
76
+ assert order.stop_price is not None
77
+
78
+ triggered = False
79
+ match order.side:
80
+ case models.OrderSide.BUY:
81
+ triggered = event.high >= order.stop_price
82
+ case models.OrderSide.SELL:
83
+ triggered = event.low <= order.stop_price
84
+
85
+ if not triggered:
86
+ continue
87
+
88
+ fill_price = 0.0
89
+ match order.side:
90
+ case models.OrderSide.BUY:
91
+ fill_price = max(order.stop_price, event.open)
92
+ case models.OrderSide.SELL:
93
+ fill_price = min(order.stop_price, event.open)
94
+
95
+ self._publish(
96
+ events.OrderFilled(
97
+ ts_event=event.ts_event,
98
+ ts_broker=event.ts_event,
99
+ associated_order_id=order.order_id,
100
+ symbol=order.symbol,
101
+ side=order.side,
102
+ quantity_filled=order.quantity,
103
+ fill_price=fill_price,
104
+ commission=max(
105
+ order.quantity * self.commission_per_unit,
106
+ self.minimum_commission_per_order,
107
+ ),
108
+ )
109
+ )
110
+ del self._pending_stop_orders[order_id]
111
+
112
+ def _process_stop_limit_orders(self, event: events.BarReceived) -> None:
113
+ for order_id, order in list(self._pending_stop_limit_orders.items()):
114
+ if order.symbol != event.symbol:
115
+ continue
116
+
117
+ # This is for mypy, it has already been validated on submission
118
+ assert order.stop_price is not None
119
+
120
+ triggered = False
121
+ match order.side:
122
+ case models.OrderSide.BUY:
123
+ triggered = event.high >= order.stop_price
124
+ case models.OrderSide.SELL:
125
+ triggered = event.low <= order.stop_price
126
+
127
+ if not triggered:
128
+ continue
129
+
130
+ limit_order = dataclasses.replace(order, order_type=models.OrderType.LIMIT)
131
+ self._pending_limit_orders[order_id] = limit_order
132
+ del self._pending_stop_limit_orders[order_id]
133
+
134
+ def _process_limit_orders(self, event: events.BarReceived) -> None:
135
+ for order_id, order in list(self._pending_limit_orders.items()):
136
+ if order.symbol != event.symbol:
137
+ continue
138
+
139
+ # This is for mypy, it has already been validated on submission
140
+ assert order.limit_price is not None
141
+
142
+ triggered = False
143
+ match order.side:
144
+ case models.OrderSide.BUY:
145
+ triggered = event.low <= order.limit_price
146
+ case models.OrderSide.SELL:
147
+ triggered = event.high >= order.limit_price
148
+
149
+ if not triggered:
150
+ continue
151
+
152
+ fill_price = 0.0
153
+ match order.side:
154
+ case models.OrderSide.BUY:
155
+ fill_price = min(order.limit_price, event.open)
156
+ case models.OrderSide.SELL:
157
+ fill_price = max(order.limit_price, event.open)
158
+
159
+ self._publish(
160
+ events.OrderFilled(
161
+ ts_event=event.ts_event,
162
+ ts_broker=event.ts_event,
163
+ associated_order_id=order.order_id,
164
+ symbol=order.symbol,
165
+ side=order.side,
166
+ quantity_filled=order.quantity,
167
+ fill_price=fill_price,
168
+ commission=max(
169
+ order.quantity * self.commission_per_unit,
170
+ self.minimum_commission_per_order,
171
+ ),
172
+ )
173
+ )
174
+ del self._pending_limit_orders[order_id]
175
+
176
+ def _reject_if_invalid_submission(self, event: events.OrderSubmission) -> bool:
177
+ is_invalid = event.quantity <= 0
178
+
179
+ match event.order_type:
180
+ case models.OrderType.LIMIT:
181
+ is_invalid = (
182
+ is_invalid or event.limit_price is None or event.limit_price <= 0
183
+ )
184
+ case models.OrderType.STOP:
185
+ is_invalid = (
186
+ is_invalid or event.stop_price is None or event.stop_price <= 0
187
+ )
188
+ case models.OrderType.STOP_LIMIT:
189
+ is_invalid = is_invalid or (
190
+ event.limit_price is None
191
+ or event.limit_price <= 0
192
+ or event.stop_price is None
193
+ or event.stop_price <= 0
194
+ )
195
+
196
+ if is_invalid:
197
+ # Use event timestamp to maintain simulated time consistency in backtesting
198
+ self._publish(
199
+ events.OrderSubmissionRejected(
200
+ ts_event=event.ts_event,
201
+ ts_broker=event.ts_event,
202
+ associated_order_id=event.system_order_id,
203
+ )
204
+ )
205
+
206
+ return is_invalid
207
+
208
+ def _on_submit_order(self, event: events.OrderSubmission) -> None:
209
+ if self._reject_if_invalid_submission(event):
210
+ return
211
+
212
+ order = _PendingOrder(
213
+ order_id=event.system_order_id,
214
+ symbol=event.symbol,
215
+ order_type=event.order_type,
216
+ side=event.side,
217
+ quantity=event.quantity,
218
+ limit_price=event.limit_price,
219
+ stop_price=event.stop_price,
220
+ )
221
+
222
+ match order.order_type:
223
+ case models.OrderType.MARKET:
224
+ self._pending_market_orders[order.order_id] = order
225
+ case models.OrderType.LIMIT:
226
+ self._pending_limit_orders[order.order_id] = order
227
+ case models.OrderType.STOP:
228
+ self._pending_stop_orders[order.order_id] = order
229
+ case models.OrderType.STOP_LIMIT:
230
+ self._pending_stop_limit_orders[order.order_id] = order
231
+
232
+ # Use event timestamp to maintain simulated time consistency in backtesting
233
+ self._publish(
234
+ events.OrderSubmissionAccepted(
235
+ ts_event=event.ts_event,
236
+ ts_broker=event.ts_event,
237
+ associated_order_id=order.order_id,
238
+ )
239
+ )
240
+
241
+ def _on_cancel_order(self, event: events.OrderCancellation) -> None:
242
+ order_id = event.system_order_id
243
+
244
+ removed = False
245
+ for pending_orders in (
246
+ self._pending_market_orders,
247
+ self._pending_limit_orders,
248
+ self._pending_stop_orders,
249
+ self._pending_stop_limit_orders,
250
+ ):
251
+ if order_id in pending_orders:
252
+ del pending_orders[order_id]
253
+ removed = True
254
+ break
255
+
256
+ # Use event timestamp to maintain simulated time consistency in backtesting
257
+ if removed:
258
+ self._publish(
259
+ events.OrderCancellationAccepted(
260
+ ts_event=event.ts_event,
261
+ ts_broker=event.ts_event,
262
+ associated_order_id=order_id,
263
+ )
264
+ )
265
+ else:
266
+ self._publish(
267
+ events.OrderCancellationRejected(
268
+ ts_event=event.ts_event,
269
+ ts_broker=event.ts_event,
270
+ associated_order_id=order_id,
271
+ )
272
+ )
273
+
274
+ def _reject_if_invalid_modification(self, event: events.OrderModification) -> bool:
275
+ is_invalid = (
276
+ (event.quantity is not None and event.quantity <= 0)
277
+ or (event.limit_price is not None and event.limit_price <= 0)
278
+ or (event.stop_price is not None and event.stop_price <= 0)
279
+ )
280
+
281
+ if is_invalid:
282
+ # Use event timestamp to maintain simulated time consistency in backtesting
283
+ self._publish(
284
+ events.OrderModificationRejected(
285
+ ts_event=event.ts_event,
286
+ ts_broker=event.ts_event,
287
+ associated_order_id=event.system_order_id,
288
+ )
289
+ )
290
+
291
+ return is_invalid
292
+
293
+ def _on_modify_order(self, event: events.OrderModification) -> None:
294
+ if self._reject_if_invalid_modification(event):
295
+ return
296
+
297
+ order_id = event.system_order_id
298
+
299
+ for pending_orders in (
300
+ self._pending_market_orders,
301
+ self._pending_limit_orders,
302
+ self._pending_stop_orders,
303
+ self._pending_stop_limit_orders,
304
+ ):
305
+ if order_id in pending_orders:
306
+ order = pending_orders[order_id]
307
+
308
+ new_quantity = (
309
+ event.quantity if event.quantity is not None else order.quantity
310
+ )
311
+ new_limit_price = (
312
+ event.limit_price
313
+ if event.limit_price is not None
314
+ else order.limit_price
315
+ )
316
+ new_stop_price = (
317
+ event.stop_price
318
+ if event.stop_price is not None
319
+ else order.stop_price
320
+ )
321
+
322
+ pending_orders[order_id] = dataclasses.replace(
323
+ order,
324
+ quantity=new_quantity,
325
+ limit_price=new_limit_price,
326
+ stop_price=new_stop_price,
327
+ )
328
+
329
+ # Use event timestamp to maintain simulated time consistency in backtesting
330
+ self._publish(
331
+ events.OrderModificationAccepted(
332
+ ts_event=event.ts_event,
333
+ ts_broker=event.ts_event,
334
+ associated_order_id=order_id,
335
+ )
336
+ )
337
+ return
338
+
339
+ # Use event timestamp to maintain simulated time consistency in backtesting
340
+ self._publish(
341
+ events.OrderModificationRejected(
342
+ ts_event=event.ts_event,
343
+ ts_broker=event.ts_event,
344
+ associated_order_id=order_id,
345
+ )
346
+ )
@@ -0,0 +1,33 @@
1
+ __all__ = [
2
+ "EventBase",
3
+ "MarketEvent",
4
+ "BrokerRequestEvent",
5
+ "BrokerResponseEvent",
6
+ "BarReceived",
7
+ "BarProcessed",
8
+ "OrderSubmission",
9
+ "OrderModification",
10
+ "OrderCancellation",
11
+ "OrderSubmissionAccepted",
12
+ "OrderSubmissionRejected",
13
+ "OrderModificationAccepted",
14
+ "OrderModificationRejected",
15
+ "OrderCancellationAccepted",
16
+ "OrderCancellationRejected",
17
+ "OrderFilled",
18
+ "OrderExpired",
19
+ ]
20
+
21
+ from .bases import EventBase, MarketEvent, BrokerRequestEvent, BrokerResponseEvent
22
+ from .market import BarReceived, BarProcessed
23
+ from .requests import OrderSubmission, OrderModification, OrderCancellation
24
+ from .responses import (
25
+ OrderSubmissionAccepted,
26
+ OrderSubmissionRejected,
27
+ OrderModificationAccepted,
28
+ OrderModificationRejected,
29
+ OrderCancellationAccepted,
30
+ OrderCancellationRejected,
31
+ OrderFilled,
32
+ OrderExpired,
33
+ )
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import pandas as pd
5
+ import uuid
6
+
7
+
8
+ @dataclasses.dataclass(kw_only=True, frozen=True)
9
+ class EventBase:
10
+ ts_event: pd.Timestamp
11
+ ts_created: pd.Timestamp = dataclasses.field(
12
+ default_factory=lambda: pd.Timestamp.now(tz="UTC")
13
+ )
14
+
15
+
16
+ @dataclasses.dataclass(kw_only=True, frozen=True)
17
+ class MarketEvent(EventBase):
18
+ pass
19
+
20
+
21
+ @dataclasses.dataclass(kw_only=True, frozen=True)
22
+ class BrokerRequestEvent(EventBase):
23
+ system_order_id: uuid.UUID
24
+
25
+
26
+ @dataclasses.dataclass(kw_only=True, frozen=True)
27
+ class BrokerResponseEvent(EventBase):
28
+ ts_broker: pd.Timestamp
29
+ associated_order_id: uuid.UUID
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+
5
+ from onesecondtrader import models
6
+ from . import bases
7
+
8
+
9
+ @dataclasses.dataclass(kw_only=True, frozen=True)
10
+ class BarReceived(bases.MarketEvent):
11
+ symbol: str
12
+ bar_period: models.data.BarPeriod
13
+ open: float
14
+ high: float
15
+ low: float
16
+ close: float
17
+ volume: int | None = None
18
+
19
+
20
+ @dataclasses.dataclass(kw_only=True, frozen=True)
21
+ class BarProcessed(BarReceived):
22
+ indicators: dict[str, float] = dataclasses.field(default_factory=dict)
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import uuid
5
+
6
+ from onesecondtrader import models
7
+ from . import bases
8
+
9
+
10
+ @dataclasses.dataclass(kw_only=True, frozen=True)
11
+ class OrderSubmission(bases.BrokerRequestEvent):
12
+ system_order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
13
+ symbol: str
14
+ order_type: models.orders.OrderType
15
+ side: models.orders.OrderSide
16
+ quantity: float
17
+ limit_price: float | None = None
18
+ stop_price: float | None = None
19
+
20
+
21
+ @dataclasses.dataclass(kw_only=True, frozen=True)
22
+ class OrderCancellation(bases.BrokerRequestEvent):
23
+ symbol: str
24
+
25
+
26
+ @dataclasses.dataclass(kw_only=True, frozen=True)
27
+ class OrderModification(bases.BrokerRequestEvent):
28
+ symbol: str
29
+ quantity: float | None = None
30
+ limit_price: float | None = None
31
+ stop_price: float | None = None
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import uuid
5
+
6
+ from onesecondtrader import models
7
+ from . import bases
8
+
9
+
10
+ @dataclasses.dataclass(kw_only=True, frozen=True)
11
+ class OrderSubmissionAccepted(bases.BrokerResponseEvent):
12
+ broker_order_id: str | None = None
13
+
14
+
15
+ @dataclasses.dataclass(kw_only=True, frozen=True)
16
+ class OrderSubmissionRejected(bases.BrokerResponseEvent):
17
+ reason: str | None = None
18
+
19
+
20
+ @dataclasses.dataclass(kw_only=True, frozen=True)
21
+ class OrderModificationAccepted(bases.BrokerResponseEvent):
22
+ broker_order_id: str | None = None
23
+
24
+
25
+ @dataclasses.dataclass(kw_only=True, frozen=True)
26
+ class OrderModificationRejected(bases.BrokerResponseEvent):
27
+ reason: str | None = None
28
+
29
+
30
+ @dataclasses.dataclass(kw_only=True, frozen=True)
31
+ class OrderCancellationAccepted(bases.BrokerResponseEvent):
32
+ pass
33
+
34
+
35
+ @dataclasses.dataclass(kw_only=True, frozen=True)
36
+ class OrderCancellationRejected(bases.BrokerResponseEvent):
37
+ reason: str | None = None
38
+
39
+
40
+ @dataclasses.dataclass(kw_only=True, frozen=True)
41
+ class OrderFilled(bases.BrokerResponseEvent):
42
+ fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
43
+ broker_fill_id: str | None = None
44
+ symbol: str
45
+ side: models.orders.OrderSide
46
+ quantity_filled: float
47
+ fill_price: float
48
+ commission: float
49
+ exchange: str = "SIMULATED"
50
+
51
+
52
+ @dataclasses.dataclass(kw_only=True, frozen=True)
53
+ class OrderExpired(bases.BrokerResponseEvent):
54
+ pass
@@ -0,0 +1,13 @@
1
+ __all__ = [
2
+ "Indicator",
3
+ "Open",
4
+ "High",
5
+ "Low",
6
+ "Close",
7
+ "Volume",
8
+ "SimpleMovingAverage",
9
+ ]
10
+
11
+ from .base import Indicator
12
+ from .bar import Open, High, Low, Close, Volume
13
+ from .averages import SimpleMovingAverage