onesecondtrader 0.14.0__py3-none-any.whl → 0.49.0__py3-none-any.whl

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 (49) hide show
  1. onesecondtrader/__init__.py +0 -12
  2. onesecondtrader/events/__init__.py +8 -0
  3. onesecondtrader/events/base.py +21 -0
  4. onesecondtrader/events/market/__init__.py +7 -0
  5. onesecondtrader/events/market/bar_processed.py +29 -0
  6. onesecondtrader/events/market/bar_received.py +34 -0
  7. onesecondtrader/events/orders/__init__.py +9 -0
  8. onesecondtrader/events/orders/base.py +30 -0
  9. onesecondtrader/events/orders/expirations.py +23 -0
  10. onesecondtrader/events/orders/fills.py +41 -0
  11. onesecondtrader/events/requests/__init__.py +11 -0
  12. onesecondtrader/events/requests/base.py +25 -0
  13. onesecondtrader/events/requests/order_cancellation.py +21 -0
  14. onesecondtrader/events/requests/order_modification.py +26 -0
  15. onesecondtrader/events/requests/order_submission.py +35 -0
  16. onesecondtrader/events/responses/__init__.py +14 -0
  17. onesecondtrader/events/responses/base.py +26 -0
  18. onesecondtrader/events/responses/cancellations.py +42 -0
  19. onesecondtrader/events/responses/modifications.py +43 -0
  20. onesecondtrader/events/responses/orders.py +42 -0
  21. onesecondtrader/indicators/__init__.py +17 -0
  22. onesecondtrader/indicators/base.py +142 -0
  23. onesecondtrader/indicators/market_fields.py +166 -0
  24. onesecondtrader/indicators/moving_averages.py +78 -106
  25. onesecondtrader/models/__init__.py +23 -0
  26. onesecondtrader/models/bar_fields.py +23 -0
  27. onesecondtrader/models/bar_period.py +21 -0
  28. onesecondtrader/models/order_types.py +21 -0
  29. onesecondtrader/models/rejection_reasons.py +48 -0
  30. onesecondtrader/models/trade_sides.py +20 -0
  31. {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/METADATA +9 -2
  32. onesecondtrader-0.49.0.dist-info/RECORD +34 -0
  33. {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/WHEEL +1 -1
  34. onesecondtrader/core/__init__.py +0 -0
  35. onesecondtrader/core/models.py +0 -188
  36. onesecondtrader/core/py.typed +0 -0
  37. onesecondtrader/datafeeds/__init__.py +0 -0
  38. onesecondtrader/datafeeds/base_datafeed.py +0 -54
  39. onesecondtrader/datafeeds/csv_datafeed.py +0 -297
  40. onesecondtrader/indicators/base_indicator.py +0 -136
  41. onesecondtrader/messaging/__init__.py +0 -9
  42. onesecondtrader/messaging/eventbus.py +0 -499
  43. onesecondtrader/messaging/events.py +0 -800
  44. onesecondtrader/monitoring/__init__.py +0 -0
  45. onesecondtrader/monitoring/console.py +0 -14
  46. onesecondtrader/monitoring/py.typed +0 -0
  47. onesecondtrader/py.typed +0 -0
  48. onesecondtrader-0.14.0.dist-info/RECORD +0 -21
  49. {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,800 +0,0 @@
1
- """
2
- This module provides the event messages used for decoupled communication between the
3
- trading infrastructure's components.
4
- Events are organized into namespaces (`Market`, `Request`, `Response`, and `System`)
5
- to provide clear semantic groupings.
6
- Base event messages used for structure inheritance are grouped under the
7
- `Base` namespace.
8
- Dataclass field validation logic is grouped under the `_Validate` namespace.
9
-
10
- ???+ note "Module Overview: `events.py`"
11
- ```mermaid
12
- ---
13
- config:
14
- themeVariables:
15
- fontSize: "11px"
16
- ---
17
- graph LR
18
-
19
- R[events.Base.Event]
20
- R1[events.Base.Market]
21
- R2[events.Base.Request]
22
- R21[events.Base.OrderRequest]
23
- R22[events.Base.CancelRequest]
24
- R3[events.Base.Response]
25
- R4[events.Base.System]
26
- R5[events.Base.Portfolio]
27
-
28
- R --> R1
29
- R --> R2
30
- R --> R3
31
- R --> R4
32
- R --> R5
33
-
34
- R2 --> R21
35
- R2 --> R22
36
-
37
- A1[events.Market.IncomingBar]
38
-
39
- R1 --> A1
40
-
41
- style A1 fill:#6F42C1,fill-opacity:0.3
42
-
43
- B1[events.Request.MarketOrder]
44
- B2[events.Request.LimitOrder]
45
- B3[events.Request.StopOrder]
46
- B4[events.Request.StopLimitOrder]
47
- B5[events.Request.CancelOrder]
48
- B6[events.Request.FlushSymbol]
49
- B7[events.Request.FlushAll]
50
-
51
- R21 --> B1
52
- R21 --> B2
53
- R21 --> B3
54
- R21 --> B4
55
- R22 --> B5
56
- R22 --> B6
57
- R22 --> B7
58
-
59
- style B1 fill:#6F42C1,fill-opacity:0.3
60
- style B2 fill:#6F42C1,fill-opacity:0.3
61
- style B3 fill:#6F42C1,fill-opacity:0.3
62
- style B4 fill:#6F42C1,fill-opacity:0.3
63
- style B5 fill:#6F42C1,fill-opacity:0.3
64
- style B6 fill:#6F42C1,fill-opacity:0.3
65
- style B7 fill:#6F42C1,fill-opacity:0.3
66
-
67
- C1[events.Response.OrderSubmitted]
68
- C2[events.Response.OrderFilled]
69
- C3[events.Response.OrderCancelled]
70
- C4[events.Response.OrderRejected]
71
-
72
- R3 --> C1
73
- R3 --> C2
74
- R3 --> C3
75
- R3 --> C4
76
-
77
- style C1 fill:#6F42C1,fill-opacity:0.3
78
- style C2 fill:#6F42C1,fill-opacity:0.3
79
- style C3 fill:#6F42C1,fill-opacity:0.3
80
- style C4 fill:#6F42C1,fill-opacity:0.3
81
-
82
- D1[events.System.Shutdown]
83
-
84
- R4 --> D1
85
-
86
- style D1 fill:#6F42C1,fill-opacity:0.3
87
-
88
- E1[events.Portfolio.SymbolRelease]
89
-
90
- R5 --> E1
91
-
92
- style E1 fill:#6F42C1,fill-opacity:0.3
93
-
94
- subgraph Market ["Market Update Event Messages"]
95
- R1
96
- A1
97
-
98
- subgraph MarketNamespace ["events.Market Namespace"]
99
- A1
100
- end
101
-
102
- end
103
-
104
-
105
- subgraph Request ["Broker Request Event Messages"]
106
- R2
107
- R21
108
- R22
109
- B1
110
- B2
111
- B3
112
- B4
113
- B5
114
- B6
115
- B7
116
-
117
- subgraph RequestNamespace ["events.Request Namespace"]
118
- B1
119
- B2
120
- B3
121
- B4
122
- B5
123
- B6
124
- B7
125
- end
126
-
127
- end
128
-
129
- subgraph Response ["Broker Response Event Messages"]
130
- R3
131
- C1
132
- C2
133
- C3
134
- C4
135
-
136
- subgraph ResponseNamespace ["events.Response Namespace"]
137
- C1
138
- C2
139
- C3
140
- C4
141
- end
142
-
143
- end
144
-
145
- subgraph System ["System-Internal Event Messages"]
146
- R4
147
- D1
148
-
149
- subgraph SystemNamespace ["events.System Namespace"]
150
- D1
151
- end
152
-
153
- end
154
-
155
- subgraph Portfolio ["Portfolio Coord. Event Messages"]
156
- R5
157
- E1
158
-
159
- subgraph PortfolioNamespace ["events.Portfolio Namespace"]
160
- E1
161
- end
162
-
163
- end
164
- ```
165
- """
166
-
167
- import dataclasses
168
- import pandas as pd
169
- import re
170
- import uuid
171
- from onesecondtrader.core import models
172
- from onesecondtrader.monitoring import console
173
-
174
-
175
- class Base:
176
- """
177
- Namespace for event base dataclasses.
178
- """
179
-
180
- @dataclasses.dataclass(kw_only=True, frozen=True)
181
- class Event:
182
- """
183
- Base event message dataclass.
184
- This dataclass cannot be instantiated directly.
185
-
186
- Attributes:
187
- ts_event (pd.Timestamp): Timestamp of the event in pandas Timestamp format.
188
- (Must be timezone-aware.)
189
- event_bus_sequence_number (int | None): Auto-generated Sequence number of
190
- the event.
191
- This will be assigned as soon as the event enters the event bus via
192
- `messaging.EventBus.publish(<event>)`.
193
- """
194
-
195
- ts_event: pd.Timestamp
196
- event_bus_sequence_number: int | None = dataclasses.field(
197
- default=None, init=False
198
- )
199
-
200
- def __new__(cls, *args, **kwargs):
201
- if cls is Base.Event:
202
- console.logger.error(
203
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
204
- )
205
- return super().__new__(cls)
206
-
207
- def __post_init__(self) -> None:
208
- _Validate.timezone_aware(self.ts_event, "ts_event", "Event")
209
-
210
- @dataclasses.dataclass(kw_only=True, frozen=True)
211
- class Market(Event):
212
- """
213
- Base event message dataclass for market events.
214
- Inherits from `Base.Event`.
215
- Each market event message is associated with a specific financial instrument via
216
- the `symbol` field.
217
- This dataclass cannot be instantiated directly.
218
-
219
- Attributes:
220
- symbol (str): Symbol of the financial instrument.
221
- """
222
-
223
- symbol: str
224
-
225
- def __new__(cls, *args, **kwargs):
226
- if cls is Base.Market:
227
- console.logger.error(
228
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
229
- )
230
- return super().__new__(cls)
231
-
232
- def __post_init__(self) -> None:
233
- super().__post_init__()
234
- _Validate.symbol(self.symbol, "Market event")
235
-
236
- @dataclasses.dataclass(kw_only=True, frozen=True)
237
- class Request(Event):
238
- """
239
- Base event message dataclass for broker requests.
240
- This dataclass cannot be instantiated directly.
241
- `ts_event` is auto-generated by default.
242
-
243
- Attributes:
244
- ts_event: Timestamp of the event. (defaults to current UTC time;
245
- auto-generated)
246
- """
247
-
248
- ts_event: pd.Timestamp = dataclasses.field(
249
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
250
- )
251
-
252
- def __new__(cls, *args, **kwargs):
253
- if cls is Base.Request:
254
- console.logger.error(
255
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
256
- )
257
- return super().__new__(cls)
258
-
259
- @dataclasses.dataclass(kw_only=True, frozen=True)
260
- class OrderRequest(Request):
261
- """
262
- Base event message dataclass for order requests.
263
- Inherits from `Base.Request`.
264
- This dataclass cannot be instantiated directly.
265
-
266
- Attributes:
267
- symbol (str): Symbol of the financial instrument.
268
- side (models.Side): Side of the order.
269
- quantity (float): Quantity of the order.
270
- time_in_force (models.TimeInForce): Time in force of the order.
271
- order_expiration (pd.Timestamp | None): Expiration timestamp of the order
272
- (optional).
273
- Only relevant if `time_in_force` is `models.TimeInForce.GTD`.
274
- order_id (uuid.UUID): Unique ID of the order. (auto-generated)
275
- """
276
-
277
- symbol: str
278
- side: models.Side
279
- quantity: float
280
- time_in_force: models.TimeInForce
281
- order_expiration: pd.Timestamp | None = None
282
- order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
283
-
284
- def __new__(cls, *args, **kwargs):
285
- if cls is Base.OrderRequest:
286
- console.logger.error(
287
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
288
- )
289
- return super().__new__(cls)
290
-
291
- def __post_init__(self) -> None:
292
- super().__post_init__()
293
- _Validate.symbol(self.symbol, f"Order {self.order_id}")
294
-
295
- _Validate.timezone_aware(
296
- self.order_expiration, "order_expiration", f"Order {self.order_id}"
297
- )
298
- _Validate.quantity(self.quantity, f"Order {self.order_id}")
299
-
300
- if self.time_in_force.value == 4:
301
- if self.order_expiration is None:
302
- console.logger.error(
303
- f"Order {self.order_id}: GTD order missing expiration "
304
- f"timestamp."
305
- )
306
- elif self.order_expiration <= self.ts_event:
307
- console.logger.error(
308
- f"Order {self.order_id}: GTD expiration "
309
- f"{self.order_expiration} "
310
- f"is not after event timestamp {self.ts_event}."
311
- )
312
-
313
- @dataclasses.dataclass(kw_only=True, frozen=True)
314
- class CancelRequest(Request):
315
- """
316
- Base event message dataclass for order cancellation requests.
317
- Inherits from `Base.Request`.
318
- This dataclass cannot be instantiated directly.
319
- """
320
-
321
- def __new__(cls, *args, **kwargs):
322
- if cls is Base.CancelRequest:
323
- console.logger.error(
324
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
325
- )
326
- return super().__new__(cls)
327
-
328
- @dataclasses.dataclass(kw_only=True, frozen=True)
329
- class Response(Event):
330
- """
331
- Base event message dataclass for broker responses.
332
- This dataclass cannot be instantiated directly.
333
- """
334
-
335
- def __new__(cls, *args, **kwargs):
336
- if cls is Base.Response:
337
- console.logger.error(
338
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
339
- )
340
- return super().__new__(cls)
341
-
342
- @dataclasses.dataclass(kw_only=True, frozen=True)
343
- class System(Event):
344
- """
345
- Base event message dataclass for system-internal messages.
346
- This dataclass cannot be instantiated directly.
347
- `ts_event` is auto-generated by default.
348
-
349
- Attributes:
350
- ts_event: Timestamp of the event. (defaults to current UTC time;
351
- auto-generated)
352
- """
353
-
354
- ts_event: pd.Timestamp = dataclasses.field(
355
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
356
- )
357
-
358
- def __new__(cls, *args, **kwargs):
359
- if cls is Base.System:
360
- console.logger.error(
361
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
362
- )
363
- return super().__new__(cls)
364
-
365
- @dataclasses.dataclass(kw_only=True, frozen=True)
366
- class Portfolio(Event):
367
- """
368
- Base event message dataclass for portfolio coordination messages.
369
- This dataclass cannot be instantiated directly.
370
-
371
- Attributes:
372
- ts_event: Timestamp of the event. (defaults to current UTC time;
373
- auto-generated)
374
- """
375
-
376
- ts_event: pd.Timestamp = dataclasses.field(
377
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
378
- )
379
-
380
- def __new__(cls, *args, **kwargs):
381
- if cls is Base.Portfolio:
382
- console.logger.error(
383
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
384
- )
385
- return super().__new__(cls)
386
-
387
-
388
- class Market:
389
- """
390
- Namespace for market update event messages.
391
- """
392
-
393
- @dataclasses.dataclass(kw_only=True, frozen=True)
394
- class IncomingBar(Base.Market):
395
- """
396
- Event message dataclass for incoming market data bars.
397
- Inherits from `Base.Market`.
398
-
399
- Attributes:
400
- bar (models.Bar): Bar of market data.
401
-
402
- Examples:
403
- >>> from onesecondtrader.messaging import events
404
- >>> from onesecondtrader.core import models
405
- >>> event = events.Market.IncomingBar(
406
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
407
- ... symbol="AAPL",
408
- ... bar=models.Bar(
409
- ... open=100.0,
410
- ... high=101.0,
411
- ... low=99.0,
412
- ... close=100.5,
413
- ... volume=10000,
414
- ... ),
415
- ... )
416
- ```
417
- """
418
-
419
- bar: models.Bar
420
-
421
-
422
- class Request:
423
- """
424
- Namespace for broker request event messages.
425
- """
426
-
427
- @dataclasses.dataclass(kw_only=True, frozen=True)
428
- class MarketOrder(Base.OrderRequest):
429
- """
430
- Event message dataclass for submitting a market order to the broker.
431
-
432
- Attributes:
433
- order_type (models.OrderType): Type of the order (automatically set to
434
- models.OrderType.MARKET).
435
-
436
- Examples:
437
- >>> from onesecondtrader.messaging import events
438
- >>> event = events.Request.MarketOrder(
439
- ... symbol="AAPL",
440
- ... side=models.Side.BUY,
441
- ... quantity=100.0,
442
- ... time_in_force=models.TimeInForce.DAY,
443
- ... )
444
- """
445
-
446
- order_type: models.OrderType = dataclasses.field(
447
- init=False, default=models.OrderType.MARKET
448
- )
449
-
450
- @dataclasses.dataclass(kw_only=True, frozen=True)
451
- class LimitOrder(Base.OrderRequest):
452
- """
453
- Event message dataclass for submitting a limit order to the broker.
454
-
455
- Attributes:
456
- order_type (models.OrderType): Type of the order (automatically set to
457
- models.OrderType.LIMIT).
458
- limit_price (float): Limit price of the order.
459
-
460
- Examples:
461
- >>> from onesecondtrader.messaging import events
462
- >>> event = events.Request.LimitOrder(
463
- ... symbol="AAPL",
464
- ... side=models.Side.BUY,
465
- ... quantity=100.0,
466
- ... time_in_force=models.TimeInForce.DAY,
467
- ... limit_price=100.0,
468
- ... )
469
- """
470
-
471
- order_type: models.OrderType = dataclasses.field(
472
- init=False, default=models.OrderType.LIMIT
473
- )
474
- limit_price: float
475
-
476
- @dataclasses.dataclass(kw_only=True, frozen=True)
477
- class StopOrder(Base.OrderRequest):
478
- """
479
- Event message dataclass for submitting a stop order to the broker.
480
-
481
- Attributes:
482
- order_type (models.OrderType): Type of the order (automatically set to
483
- models.OrderType.STOP).
484
- stop_price (float): Stop price of the order.
485
-
486
- Examples:
487
- >>> from onesecondtrader.messaging import events
488
- >>> event = events.Request.StopOrder(
489
- ... symbol="AAPL",
490
- ... side=models.Side.BUY,
491
- ... quantity=100.0,
492
- ... time_in_force=models.TimeInForce.DAY,
493
- ... stop_price=100.0,
494
- ... )
495
- """
496
-
497
- order_type: models.OrderType = dataclasses.field(
498
- init=False, default=models.OrderType.STOP
499
- )
500
- stop_price: float
501
-
502
- @dataclasses.dataclass(kw_only=True, frozen=True)
503
- class StopLimitOrder(Base.OrderRequest):
504
- """
505
- Event message dataclass for submitting a stop-limit order to the broker.
506
-
507
- Attributes:
508
- order_type (models.OrderType): Type of the order (automatically set to
509
- models.OrderType.STOP_LIMIT).
510
- stop_price (float): Stop price of the order.
511
- limit_price (float): Limit price of the order.
512
-
513
- Examples:
514
- >>> from onesecondtrader.messaging import events
515
- >>> event = events.Request.StopLimitOrder(
516
- ... symbol="AAPL",
517
- ... side=models.Side.BUY,
518
- ... quantity=100.0,
519
- ... time_in_force=models.TimeInForce.DAY,
520
- ... stop_price=100.0,
521
- ... limit_price=100.0,
522
- ... )
523
- """
524
-
525
- order_type: models.OrderType = dataclasses.field(
526
- init=False, default=models.OrderType.STOP_LIMIT
527
- )
528
- stop_price: float
529
- limit_price: float
530
-
531
- @dataclasses.dataclass(kw_only=True, frozen=True)
532
- class CancelOrder(Base.CancelRequest):
533
- """
534
- Event message dataclass for cancelling an order.
535
-
536
- Attributes:
537
- order_id (uuid.UUID): Unique ID of the order to cancel.
538
-
539
- Examples:
540
- >>> from onesecondtrader.messaging import events
541
- >>> event = events.Request.CancelOrder(
542
- ... order_id=uuid.UUID("12345678-1234-5678-1234-567812345678"),
543
- ... )
544
- """
545
-
546
- order_id: uuid.UUID
547
-
548
- @dataclasses.dataclass(kw_only=True, frozen=True)
549
- class FlushSymbol(Base.Request):
550
- """
551
- Event message dataclass for flushing all orders for a symbol.
552
-
553
- Attributes:
554
- symbol (str): Symbol to flush.
555
-
556
- Examples:
557
- >>> from onesecondtrader.messaging import events
558
- >>> event = events.Request.FlushSymbol(
559
- ... symbol="AAPL",
560
- ... )
561
- """
562
-
563
- symbol: str
564
-
565
- def __post_init__(self) -> None:
566
- super().__post_init__()
567
- _Validate.symbol(self.symbol, "Flush request")
568
-
569
- @dataclasses.dataclass(kw_only=True, frozen=True)
570
- class FlushAll(Base.Request):
571
- """
572
- Event message dataclass for flushing all orders for all symbols.
573
-
574
- Examples:
575
- >>> from onesecondtrader.messaging import events
576
- >>> event = events.Request.FlushAll()
577
- """
578
-
579
- pass
580
-
581
-
582
- class Response:
583
- """
584
- Namespace for broker response event messages.
585
- """
586
-
587
- @dataclasses.dataclass(kw_only=True, frozen=True)
588
- class OrderSubmitted(Base.Response):
589
- """
590
- Event message dataclass for order submission confirmation from the broker.
591
-
592
- Attributes:
593
- order_submitted_id (uuid.UUID): Unique ID of the submitted order.
594
- associated_request_id (uuid.UUID): Unique ID of the request that triggered
595
- the order submission.
596
-
597
- Examples:
598
- >>> from onesecondtrader.messaging import events
599
- >>> import pandas as pd
600
- >>> import uuid
601
- >>> event = events.Response.OrderSubmitted(
602
- ... ts_event=pd.Timestamp(
603
- ... "2023-01-01 00:00:00", tz="UTC"),
604
- ... order_submitted_id=uuid.UUID(
605
- ... "12345678-1234-5678-1234-567812345678"),
606
- ... associated_request_id=uuid.UUID(
607
- ... "12345678-1234-5678-1234-567812345678"
608
- ... ),
609
- ... )
610
- """
611
-
612
- order_submitted_id: uuid.UUID
613
- associated_request_id: uuid.UUID
614
-
615
- @dataclasses.dataclass(kw_only=True, frozen=True)
616
- class OrderFilled(Base.Response):
617
- """
618
- Event message dataclass for order fill confirmation from the broker.
619
-
620
- Attributes:
621
- fill_id (uuid.UUID): Unique ID of the fill. (auto-generated)
622
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
623
- that triggered the fill.
624
- side (models.Side): Side of the fill.
625
- quantity_filled (float): Quantity filled.
626
- filled_at_price (float): Price at which the fill was executed.
627
- commission_and_fees (float): Commission and fees for the fill.
628
- net_fill_value (float): Net fill value (auto-generated).
629
- exchange (str | None): Exchange on which the fill was executed. (optional;
630
- defaults to "SIMULATED")
631
-
632
- Examples:
633
- >>> from onesecondtrader.messaging import events
634
- >>> from onesecondtrader.core import models
635
- >>> import pandas as pd
636
- >>> import uuid
637
- >>> event = events.Response.OrderFilled(
638
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
639
- ... associated_order_submitted_id=uuid.UUID(
640
- ... "12345678-1234-5678-1234-567812345678"
641
- ... ),
642
- ... side=models.Side.BUY,
643
- ... quantity_filled=100.0,
644
- ... filled_at_price=100.0,
645
- ... commission_and_fees=1.0,
646
- ... )
647
- """
648
-
649
- fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
650
- associated_order_submitted_id: uuid.UUID
651
- side: models.Side
652
- quantity_filled: float
653
- filled_at_price: float
654
- commission_and_fees: float
655
- net_fill_value: float = dataclasses.field(init=False)
656
- exchange: str | None = None
657
-
658
- def __post_init__(self):
659
- object.__setattr__(self, "fill_id", self.fill_id or uuid.uuid4())
660
-
661
- gross_value = self.filled_at_price * self.quantity_filled
662
-
663
- if self.side.value == 1:
664
- net_value = gross_value + self.commission_and_fees
665
- else:
666
- net_value = gross_value - self.commission_and_fees
667
-
668
- object.__setattr__(self, "net_fill_value", net_value)
669
-
670
- object.__setattr__(self, "exchange", self.exchange or "SIMULATED")
671
-
672
- @dataclasses.dataclass(kw_only=True, frozen=True)
673
- class OrderCancelled(Base.Response):
674
- """
675
- Event message dataclass for order cancellation confirmation from the broker.
676
-
677
- Attributes:
678
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
679
- that was cancelled.
680
-
681
- Examples:
682
- >>> from onesecondtrader.messaging import events
683
- >>> import pandas as pd
684
- >>> import uuid
685
- >>> event = events.Response.OrderCancelled(
686
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
687
- ... associated_order_submitted_id=uuid.UUID(
688
- ... "12345678-1234-5678-1234-567812345678"
689
- ... ),
690
- ... )
691
- """
692
-
693
- associated_order_submitted_id: uuid.UUID
694
-
695
- @dataclasses.dataclass(kw_only=True, frozen=True)
696
- class OrderRejected(Base.Response):
697
- """
698
- Event message dataclass for order rejection confirmation from the broker.
699
-
700
- Attributes:
701
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
702
- that was rejected.
703
- reason (models.OrderRejectionReason): Reason for the rejection.
704
-
705
- Examples:
706
- >>> from onesecondtrader.messaging import events
707
- >>> from onesecondtrader.core import models
708
- >>> import pandas as pd
709
- >>> import uuid
710
- >>> event = events.Response.OrderRejected(
711
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
712
- ... associated_order_submitted_id=uuid.UUID(
713
- ... "12345678-1234-5678-1234-567812345678"
714
- ... ),
715
- ... reason=models.OrderRejectionReason.NEGATIVE_QUANTITY,
716
- ... )
717
- """
718
-
719
- associated_order_submitted_id: uuid.UUID
720
- reason: models.OrderRejectionReason
721
-
722
-
723
- class System:
724
- """
725
- Namespace for system-internal event messages.
726
- """
727
-
728
- @dataclasses.dataclass(kw_only=True, frozen=True)
729
- class Shutdown(Base.System):
730
- """
731
- Event message dataclass for system shutdown.
732
-
733
- Examples:
734
- >>> from onesecondtrader.messaging import events
735
- >>> event = events.System.Shutdown()
736
- """
737
-
738
- pass
739
-
740
-
741
- class Portfolio:
742
- """
743
- Namespace for portfolio coordination event messages.
744
- """
745
-
746
- @dataclasses.dataclass(kw_only=True, frozen=True)
747
- class SymbolRelease(Base.Portfolio):
748
- """
749
- Event to indicate a strategy releases ownership of a symbol.
750
- """
751
-
752
- symbol: str
753
- strategy_name: str
754
-
755
- def __post_init__(self) -> None:
756
- super().__post_init__()
757
- _Validate.symbol(self.symbol, "Portfolio.SymbolRelease")
758
-
759
-
760
- class _Validate:
761
- """Internal validation utilities for events."""
762
-
763
- @staticmethod
764
- def symbol(symbol: str, context: str = "") -> None:
765
- """Validate symbol format and log errors."""
766
- if not symbol or not symbol.strip():
767
- console.logger.error(f"{context}: Symbol cannot be empty or whitespace")
768
- return
769
-
770
- if not re.fullmatch(r"[A-Z0-9._-]+", symbol):
771
- console.logger.error(f"{context}: Invalid symbol format: {symbol}")
772
-
773
- @staticmethod
774
- def quantity(quantity: float, context: str = "") -> None:
775
- """Validate quantity values and log errors."""
776
- if (
777
- quantity != quantity
778
- or quantity == float("inf")
779
- or quantity == float("-inf")
780
- ):
781
- console.logger.error(f"{context}: quantity cannot be NaN or infinite")
782
- return
783
-
784
- if quantity <= 0:
785
- console.logger.error(
786
- f"{context}: quantity must be positive, got {quantity}"
787
- )
788
-
789
- if quantity > 1e9:
790
- console.logger.error(f"{context}: quantity too large: {quantity}")
791
-
792
- @staticmethod
793
- def timezone_aware(
794
- timestamp: pd.Timestamp | None, field_name: str, context: str = ""
795
- ) -> None:
796
- """Validate that timestamp is timezone-aware and log errors."""
797
- if timestamp is not None and timestamp.tz is None:
798
- console.logger.error(
799
- f"{context}: {field_name} must be timezone-aware, got {timestamp}"
800
- )