onesecondtrader 0.15.0__py3-none-any.whl → 0.16.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.
@@ -1,894 +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 (`Strategy`, `Market`, `Request`, `Response`, and
5
- `System`) 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.Strategy]
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.Strategy.SymbolRelease]
89
- E2[events.Strategy.SymbolAssignment]
90
- E3[events.Strategy.StopTrading]
91
- E4[events.Strategy.StopTradingSymbol]
92
-
93
- R5 --> E1
94
- R5 --> E2
95
- R5 --> E3
96
- R5 --> E4
97
-
98
- style E1 fill:#6F42C1,fill-opacity:0.3
99
- style E2 fill:#6F42C1,fill-opacity:0.3
100
- style E3 fill:#6F42C1,fill-opacity:0.3
101
- style E4 fill:#6F42C1,fill-opacity:0.3
102
-
103
- subgraph Market ["Market Update Event Messages"]
104
- R1
105
- A1
106
-
107
- subgraph MarketNamespace ["events.Market Namespace"]
108
- A1
109
- end
110
-
111
- end
112
-
113
-
114
- subgraph Request ["Broker Request Event Messages"]
115
- R2
116
- R21
117
- R22
118
- B1
119
- B2
120
- B3
121
- B4
122
- B5
123
- B6
124
- B7
125
-
126
- subgraph RequestNamespace ["events.Request Namespace"]
127
- B1
128
- B2
129
- B3
130
- B4
131
- B5
132
- B6
133
- B7
134
- end
135
-
136
- end
137
-
138
- subgraph Response ["Broker Response Event Messages"]
139
- R3
140
- C1
141
- C2
142
- C3
143
- C4
144
-
145
- subgraph ResponseNamespace ["events.Response Namespace"]
146
- C1
147
- C2
148
- C3
149
- C4
150
- end
151
-
152
- end
153
-
154
- subgraph System ["System-Internal Event Messages"]
155
- R4
156
- D1
157
-
158
- subgraph SystemNamespace ["events.System Namespace"]
159
- D1
160
- end
161
-
162
- end
163
-
164
- subgraph Strategy ["Strategy Coord. Event Messages"]
165
- R5
166
- E1
167
- E2
168
- E3
169
- E4
170
-
171
- subgraph StrategyNamespace ["events.Strategy Namespace"]
172
- E1
173
- E2
174
- E3
175
- E4
176
- end
177
-
178
- end
179
- ```
180
- """
181
-
182
- import dataclasses
183
- import pandas as pd
184
- import re
185
- import uuid
186
- from onesecondtrader.core import models
187
- from onesecondtrader.monitoring import console
188
- from onesecondtrader.strategies import base_strategy
189
-
190
-
191
- class Base:
192
- """
193
- Namespace for event base dataclasses.
194
- """
195
-
196
- @dataclasses.dataclass(kw_only=True, frozen=True)
197
- class Event:
198
- """
199
- Base event message dataclass.
200
- This dataclass cannot be instantiated directly.
201
-
202
- Attributes:
203
- ts_event (pd.Timestamp): Timestamp of the event in pandas Timestamp format.
204
- (Must be timezone-aware.)
205
- event_bus_sequence_number (int | None): Auto-generated Sequence number of
206
- the event.
207
- This will be assigned as soon as the event enters the event bus via
208
- `messaging.EventBus.publish(<event>)`.
209
- """
210
-
211
- ts_event: pd.Timestamp
212
- event_bus_sequence_number: int | None = dataclasses.field(
213
- default=None, init=False
214
- )
215
-
216
- def __new__(cls, *args, **kwargs):
217
- if cls is Base.Event:
218
- console.logger.error(
219
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
220
- )
221
- return super().__new__(cls)
222
-
223
- def __post_init__(self) -> None:
224
- _Validate.timezone_aware(self.ts_event, "ts_event", "Event")
225
-
226
- @dataclasses.dataclass(kw_only=True, frozen=True)
227
- class Market(Event):
228
- """
229
- Base event message dataclass for market events.
230
- Inherits from `Base.Event`.
231
- Each market event message is associated with a specific financial instrument via
232
- the `symbol` field.
233
- This dataclass cannot be instantiated directly.
234
-
235
- Attributes:
236
- symbol (str): Symbol of the financial instrument.
237
- """
238
-
239
- symbol: str
240
-
241
- def __new__(cls, *args, **kwargs):
242
- if cls is Base.Market:
243
- console.logger.error(
244
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
245
- )
246
- return super().__new__(cls)
247
-
248
- def __post_init__(self) -> None:
249
- super().__post_init__()
250
-
251
- @dataclasses.dataclass(kw_only=True, frozen=True)
252
- class Request(Event):
253
- """
254
- Base event message dataclass for broker requests.
255
- This dataclass cannot be instantiated directly.
256
- `ts_event` is auto-generated by default.
257
-
258
- Attributes:
259
- ts_event: Timestamp of the event. (defaults to current UTC time;
260
- auto-generated)
261
- """
262
-
263
- ts_event: pd.Timestamp = dataclasses.field(
264
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
265
- )
266
-
267
- def __new__(cls, *args, **kwargs):
268
- if cls is Base.Request:
269
- console.logger.error(
270
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
271
- )
272
- return super().__new__(cls)
273
-
274
- @dataclasses.dataclass(kw_only=True, frozen=True)
275
- class OrderRequest(Request):
276
- """
277
- Base event message dataclass for order requests.
278
- Inherits from `Base.Request`.
279
- This dataclass cannot be instantiated directly.
280
-
281
- Attributes:
282
- symbol (str): Symbol of the financial instrument.
283
- side (models.Side): Side of the order.
284
- quantity (float): Quantity of the order.
285
- time_in_force (models.TimeInForce): Time in force of the order.
286
- order_expiration (pd.Timestamp | None): Expiration timestamp of the order
287
- (optional).
288
- Only relevant if `time_in_force` is `models.TimeInForce.GTD`.
289
- order_id (uuid.UUID): Unique ID of the order. (auto-generated)
290
- """
291
-
292
- symbol: str
293
- side: models.Side
294
- quantity: float
295
- time_in_force: models.TimeInForce
296
- order_expiration: pd.Timestamp | None = None
297
- order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
298
-
299
- def __new__(cls, *args, **kwargs):
300
- if cls is Base.OrderRequest:
301
- console.logger.error(
302
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
303
- )
304
- return super().__new__(cls)
305
-
306
- def __post_init__(self) -> None:
307
- super().__post_init__()
308
-
309
- _Validate.timezone_aware(
310
- self.order_expiration, "order_expiration", f"Order {self.order_id}"
311
- )
312
- _Validate.quantity(self.quantity, f"Order {self.order_id}")
313
-
314
- if self.time_in_force is models.TimeInForce.GTD:
315
- if self.order_expiration is None:
316
- console.logger.error(
317
- f"Order {self.order_id}: GTD order missing expiration "
318
- f"timestamp."
319
- )
320
- elif self.order_expiration <= self.ts_event:
321
- console.logger.error(
322
- f"Order {self.order_id}: GTD expiration "
323
- f"{self.order_expiration} "
324
- f"is not after event timestamp {self.ts_event}."
325
- )
326
-
327
- @dataclasses.dataclass(kw_only=True, frozen=True)
328
- class CancelRequest(Request):
329
- """
330
- Base event message dataclass for order cancellation requests.
331
- Inherits from `Base.Request`.
332
- This dataclass cannot be instantiated directly.
333
- """
334
-
335
- def __new__(cls, *args, **kwargs):
336
- if cls is Base.CancelRequest:
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 Response(Event):
344
- """
345
- Base event message dataclass for broker responses.
346
- This dataclass cannot be instantiated directly.
347
- """
348
-
349
- def __new__(cls, *args, **kwargs):
350
- if cls is Base.Response:
351
- console.logger.error(
352
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
353
- )
354
- return super().__new__(cls)
355
-
356
- @dataclasses.dataclass(kw_only=True, frozen=True)
357
- class System(Event):
358
- """
359
- Base event message dataclass for system-internal messages.
360
- This dataclass cannot be instantiated directly.
361
- `ts_event` is auto-generated by default.
362
-
363
- Attributes:
364
- ts_event: Timestamp of the event. (defaults to current UTC time;
365
- auto-generated)
366
- """
367
-
368
- ts_event: pd.Timestamp = dataclasses.field(
369
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
370
- )
371
-
372
- def __new__(cls, *args, **kwargs):
373
- if cls is Base.System:
374
- console.logger.error(
375
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
376
- )
377
- return super().__new__(cls)
378
-
379
- @dataclasses.dataclass(kw_only=True, frozen=True)
380
- class Strategy(Event):
381
- """
382
- Base event message dataclass for strategy coordination messages.
383
- This dataclass cannot be instantiated directly.
384
-
385
- Attributes:
386
- ts_event: Timestamp of the event. (defaults to current UTC time;
387
- auto-generated)
388
- """
389
-
390
- ts_event: pd.Timestamp = dataclasses.field(
391
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
392
- )
393
- strategy: base_strategy.Strategy
394
-
395
- def __new__(cls, *args, **kwargs):
396
- if cls is Base.Strategy:
397
- console.logger.error(
398
- f"Cannot instantiate abstract class '{cls.__name__}' directly"
399
- )
400
- return super().__new__(cls)
401
-
402
- def __post_init__(self) -> None:
403
- super().__post_init__()
404
- if not isinstance(self.strategy, base_strategy.Strategy):
405
- console.logger.error(
406
- f"{type(self).__name__}: strategy must inherit from Strategy"
407
- )
408
-
409
-
410
- class Market:
411
- """
412
- Namespace for market update event messages.
413
- """
414
-
415
- @dataclasses.dataclass(kw_only=True, frozen=True)
416
- class IncomingBar(Base.Market):
417
- """
418
- Event message dataclass for incoming market data bars.
419
- Inherits from `Base.Market`.
420
-
421
- Attributes:
422
- bar (models.Bar): Bar of market data.
423
-
424
- Examples:
425
- >>> from onesecondtrader.messaging import events
426
- >>> from onesecondtrader.core import models
427
- >>> event = events.Market.IncomingBar(
428
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
429
- ... symbol="AAPL",
430
- ... bar=models.Bar(
431
- ... open=100.0,
432
- ... high=101.0,
433
- ... low=99.0,
434
- ... close=100.5,
435
- ... volume=10000,
436
- ... ),
437
- ... )
438
-
439
- """
440
-
441
- bar: models.Bar
442
-
443
-
444
- class Request:
445
- """
446
- Namespace for broker request event messages.
447
- """
448
-
449
- @dataclasses.dataclass(kw_only=True, frozen=True)
450
- class MarketOrder(Base.OrderRequest):
451
- """
452
- Event message dataclass for submitting a market order to the broker.
453
-
454
- Attributes:
455
- order_type (models.OrderType): Type of the order (automatically set to
456
- models.OrderType.MARKET).
457
-
458
- Examples:
459
- >>> from onesecondtrader.messaging import events
460
- >>> event = events.Request.MarketOrder(
461
- ... symbol="AAPL",
462
- ... side=models.Side.BUY,
463
- ... quantity=100.0,
464
- ... time_in_force=models.TimeInForce.DAY,
465
- ... )
466
- """
467
-
468
- order_type: models.OrderType = dataclasses.field(
469
- init=False, default=models.OrderType.MARKET
470
- )
471
-
472
- @dataclasses.dataclass(kw_only=True, frozen=True)
473
- class LimitOrder(Base.OrderRequest):
474
- """
475
- Event message dataclass for submitting a limit order to the broker.
476
-
477
- Attributes:
478
- order_type (models.OrderType): Type of the order (automatically set to
479
- models.OrderType.LIMIT).
480
- limit_price (float): Limit price of the order.
481
-
482
- Examples:
483
- >>> from onesecondtrader.messaging import events
484
- >>> event = events.Request.LimitOrder(
485
- ... symbol="AAPL",
486
- ... side=models.Side.BUY,
487
- ... quantity=100.0,
488
- ... time_in_force=models.TimeInForce.DAY,
489
- ... limit_price=100.0,
490
- ... )
491
- """
492
-
493
- order_type: models.OrderType = dataclasses.field(
494
- init=False, default=models.OrderType.LIMIT
495
- )
496
- limit_price: float
497
-
498
- @dataclasses.dataclass(kw_only=True, frozen=True)
499
- class StopOrder(Base.OrderRequest):
500
- """
501
- Event message dataclass for submitting a stop order to the broker.
502
-
503
- Attributes:
504
- order_type (models.OrderType): Type of the order (automatically set to
505
- models.OrderType.STOP).
506
- stop_price (float): Stop price of the order.
507
-
508
- Examples:
509
- >>> from onesecondtrader.messaging import events
510
- >>> event = events.Request.StopOrder(
511
- ... symbol="AAPL",
512
- ... side=models.Side.BUY,
513
- ... quantity=100.0,
514
- ... time_in_force=models.TimeInForce.DAY,
515
- ... stop_price=100.0,
516
- ... )
517
- """
518
-
519
- order_type: models.OrderType = dataclasses.field(
520
- init=False, default=models.OrderType.STOP
521
- )
522
- stop_price: float
523
-
524
- @dataclasses.dataclass(kw_only=True, frozen=True)
525
- class StopLimitOrder(Base.OrderRequest):
526
- """
527
- Event message dataclass for submitting a stop-limit order to the broker.
528
-
529
- Attributes:
530
- order_type (models.OrderType): Type of the order (automatically set to
531
- models.OrderType.STOP_LIMIT).
532
- stop_price (float): Stop price of the order.
533
- limit_price (float): Limit price of the order.
534
-
535
- Examples:
536
- >>> from onesecondtrader.messaging import events
537
- >>> event = events.Request.StopLimitOrder(
538
- ... symbol="AAPL",
539
- ... side=models.Side.BUY,
540
- ... quantity=100.0,
541
- ... time_in_force=models.TimeInForce.DAY,
542
- ... stop_price=100.0,
543
- ... limit_price=100.0,
544
- ... )
545
- """
546
-
547
- order_type: models.OrderType = dataclasses.field(
548
- init=False, default=models.OrderType.STOP_LIMIT
549
- )
550
- stop_price: float
551
- limit_price: float
552
-
553
- @dataclasses.dataclass(kw_only=True, frozen=True)
554
- class CancelOrder(Base.CancelRequest):
555
- """
556
- Event message dataclass for cancelling an order.
557
-
558
- Attributes:
559
- order_id (uuid.UUID): Unique ID of the order to cancel.
560
-
561
- Examples:
562
- >>> from onesecondtrader.messaging import events
563
- >>> event = events.Request.CancelOrder(
564
- ... order_id=uuid.UUID("12345678-1234-5678-1234-567812345678"),
565
- ... )
566
- """
567
-
568
- order_id: uuid.UUID
569
-
570
- @dataclasses.dataclass(kw_only=True, frozen=True)
571
- class FlushSymbol(Base.Request):
572
- """
573
- Event message dataclass for flushing all orders for a symbol.
574
-
575
- Attributes:
576
- symbol (str): Symbol to flush.
577
-
578
- Examples:
579
- >>> from onesecondtrader.messaging import events
580
- >>> event = events.Request.FlushSymbol(
581
- ... symbol="AAPL",
582
- ... )
583
- """
584
-
585
- symbol: str
586
-
587
- def __post_init__(self) -> None:
588
- super().__post_init__()
589
-
590
- @dataclasses.dataclass(kw_only=True, frozen=True)
591
- class FlushAll(Base.Request):
592
- """
593
- Event message dataclass for flushing all orders for all symbols.
594
-
595
- Examples:
596
- >>> from onesecondtrader.messaging import events
597
- >>> event = events.Request.FlushAll()
598
- """
599
-
600
- pass
601
-
602
-
603
- class Response:
604
- """
605
- Namespace for broker response event messages.
606
- """
607
-
608
- @dataclasses.dataclass(kw_only=True, frozen=True)
609
- class OrderSubmitted(Base.Response):
610
- """
611
- Event message dataclass for order submission confirmation from the broker.
612
-
613
- Attributes:
614
- order_submitted_id (uuid.UUID): Unique ID of the submitted order.
615
- associated_request_id (uuid.UUID): Unique ID of the request that triggered
616
- the order submission.
617
-
618
- Examples:
619
- >>> from onesecondtrader.messaging import events
620
- >>> import pandas as pd
621
- >>> import uuid
622
- >>> event = events.Response.OrderSubmitted(
623
- ... ts_event=pd.Timestamp(
624
- ... "2023-01-01 00:00:00", tz="UTC"),
625
- ... order_submitted_id=uuid.UUID(
626
- ... "12345678-1234-5678-1234-567812345678"),
627
- ... associated_request_id=uuid.UUID(
628
- ... "12345678-1234-5678-1234-567812345678"
629
- ... ),
630
- ... )
631
- """
632
-
633
- order_submitted_id: uuid.UUID
634
- associated_request_id: uuid.UUID
635
-
636
- @dataclasses.dataclass(kw_only=True, frozen=True)
637
- class OrderFilled(Base.Response):
638
- """
639
- Event message dataclass for order fill confirmation from the broker.
640
-
641
- Attributes:
642
- fill_id (uuid.UUID): Unique ID of the fill. (auto-generated)
643
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
644
- that triggered the fill.
645
- side (models.Side): Side of the fill.
646
- quantity_filled (float): Quantity filled.
647
- filled_at_price (float): Price at which the fill was executed.
648
- commission_and_fees (float): Commission and fees for the fill.
649
- net_fill_value (float): Net fill value (auto-generated).
650
- exchange (str | None): Exchange on which the fill was executed. (optional;
651
- defaults to "SIMULATED")
652
-
653
- Examples:
654
- >>> from onesecondtrader.messaging import events
655
- >>> from onesecondtrader.core import models
656
- >>> import pandas as pd
657
- >>> import uuid
658
- >>> event = events.Response.OrderFilled(
659
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
660
- ... associated_order_submitted_id=uuid.UUID(
661
- ... "12345678-1234-5678-1234-567812345678"
662
- ... ),
663
- ... side=models.Side.BUY,
664
- ... quantity_filled=100.0,
665
- ... filled_at_price=100.0,
666
- ... commission_and_fees=1.0,
667
- ... )
668
- """
669
-
670
- fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
671
- associated_order_submitted_id: uuid.UUID
672
- side: models.Side
673
- quantity_filled: float
674
- filled_at_price: float
675
- commission_and_fees: float
676
- net_fill_value: float = dataclasses.field(init=False)
677
- exchange: str | None = None
678
-
679
- def __post_init__(self):
680
- object.__setattr__(self, "fill_id", self.fill_id or uuid.uuid4())
681
-
682
- gross_value = self.filled_at_price * self.quantity_filled
683
-
684
- if self.side is models.Side.BUY:
685
- net_value = gross_value + self.commission_and_fees
686
- else:
687
- net_value = gross_value - self.commission_and_fees
688
-
689
- object.__setattr__(self, "net_fill_value", net_value)
690
-
691
- object.__setattr__(self, "exchange", self.exchange or "SIMULATED")
692
-
693
- @dataclasses.dataclass(kw_only=True, frozen=True)
694
- class OrderCancelled(Base.Response):
695
- """
696
- Event message dataclass for order cancellation confirmation from the broker.
697
-
698
- Attributes:
699
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
700
- that was cancelled.
701
-
702
- Examples:
703
- >>> from onesecondtrader.messaging import events
704
- >>> import pandas as pd
705
- >>> import uuid
706
- >>> event = events.Response.OrderCancelled(
707
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
708
- ... associated_order_submitted_id=uuid.UUID(
709
- ... "12345678-1234-5678-1234-567812345678"
710
- ... ),
711
- ... )
712
- """
713
-
714
- associated_order_submitted_id: uuid.UUID
715
-
716
- @dataclasses.dataclass(kw_only=True, frozen=True)
717
- class OrderRejected(Base.Response):
718
- """
719
- Event message dataclass for order rejection confirmation from the broker.
720
-
721
- Attributes:
722
- associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
723
- that was rejected.
724
- reason (models.OrderRejectionReason): Reason for the rejection.
725
-
726
- Examples:
727
- >>> from onesecondtrader.messaging import events
728
- >>> from onesecondtrader.core import models
729
- >>> import pandas as pd
730
- >>> import uuid
731
- >>> event = events.Response.OrderRejected(
732
- ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
733
- ... associated_order_submitted_id=uuid.UUID(
734
- ... "12345678-1234-5678-1234-567812345678"
735
- ... ),
736
- ... reason=models.OrderRejectionReason.NEGATIVE_QUANTITY,
737
- ... )
738
- """
739
-
740
- associated_order_submitted_id: uuid.UUID
741
- reason: models.OrderRejectionReason
742
-
743
-
744
- class System:
745
- """
746
- Namespace for system-internal event messages.
747
- """
748
-
749
- @dataclasses.dataclass(kw_only=True, frozen=True)
750
- class Shutdown(Base.System):
751
- """
752
- Event message dataclass for system shutdown.
753
-
754
- Examples:
755
- >>> from onesecondtrader.messaging import events
756
- >>> event = events.System.Shutdown()
757
- """
758
-
759
- pass
760
-
761
-
762
- class Strategy:
763
- """
764
- Namespace for strategy coordination event messages.
765
- """
766
-
767
- @dataclasses.dataclass(kw_only=True, frozen=True)
768
- class SymbolRelease(Base.Strategy):
769
- """
770
- Event to indicate a strategy releases ownership of a symbol.
771
-
772
- Attributes:
773
- symbol (str): Symbol released.
774
-
775
- Examples:
776
- >>> from onesecondtrader.messaging import events
777
- >>> event = events.Strategy.SymbolRelease(
778
- ... symbol="AAPL",
779
- ... )
780
- """
781
-
782
- symbol: str
783
-
784
- def __post_init__(self) -> None:
785
- super().__post_init__()
786
-
787
- @dataclasses.dataclass(kw_only=True, frozen=True)
788
- class SymbolAssignment(Base.Strategy):
789
- """
790
- Event message to indicate that a symbol should be assigned to a strategy.
791
-
792
- Attributes:
793
- symbol_list (list[str]): List of symbols to be assigned.
794
-
795
- Examples:
796
- >>> from onesecondtrader.messaging import events
797
- >>> event = events.Strategy.SymbolAssignment(
798
- ... strategy=my_strategy,
799
- ... symbol=["AAPL"],
800
- ... )
801
- """
802
-
803
- symbol_list: list[str]
804
-
805
- def __post_init__(self) -> None:
806
- super().__post_init__()
807
-
808
- @dataclasses.dataclass(kw_only=True, frozen=True)
809
- class StopTrading(Base.Strategy):
810
- """
811
- Event to indicate a strategy should stop trading.
812
-
813
- Attributes:
814
- shutdown_mode (models.StrategyShutdownMode): Shutdown mode to use.
815
- Defaults to `SOFT`.
816
-
817
- Examples:
818
- >>> from onesecondtrader.messaging import events
819
- >>> event = events.Strategy.StopTrading(
820
- ... strategy=my_strategy,
821
- ... shutdown_mode=models.StrategyShutdownMode.SOFT,
822
- ... )
823
- """
824
-
825
- shutdown_mode: models.StrategyShutdownMode
826
-
827
- @dataclasses.dataclass(kw_only=True, frozen=True)
828
- class StopTradingSymbol(Base.Strategy):
829
- """
830
- Event to indicate a strategy should stop trading a symbol.
831
-
832
- Attributes:
833
- symbol (str): Symbol to stop trading.
834
- shutdown_mode (models.SymbolShutdownMode): Shutdown mode to use.
835
- Defaults to `SOFT`.
836
-
837
- Examples:
838
- >>> from onesecondtrader.messaging import events
839
- >>> event = events.Strategy.StopTradingSymbol(
840
- ... strategy=my_strategy,
841
- ... symbol="AAPL",
842
- ... shutdown_mode=models.SymbolShutdownMode.HARD,
843
- ... )
844
- """
845
-
846
- symbol: str
847
- shutdown_mode: models.SymbolShutdownMode = models.SymbolShutdownMode.SOFT
848
-
849
- def __post_init__(self) -> None:
850
- super().__post_init__()
851
- _Validate.symbol(self.symbol, f"StopTradingSymbol {self.symbol}")
852
-
853
-
854
- class _Validate:
855
- """Internal validation utilities for events."""
856
-
857
- @staticmethod
858
- def symbol(symbol: str, context: str = "") -> None:
859
- """Validate symbol format and log errors."""
860
- if not symbol or not symbol.strip():
861
- console.logger.error(f"{context}: Symbol cannot be empty or whitespace")
862
- return
863
-
864
- if not re.fullmatch(r"[A-Z0-9._-]+", symbol):
865
- console.logger.error(f"{context}: Invalid symbol format: {symbol}")
866
-
867
- @staticmethod
868
- def quantity(quantity: float, context: str = "") -> None:
869
- """Validate quantity values and log errors."""
870
- if (
871
- quantity != quantity
872
- or quantity == float("inf")
873
- or quantity == float("-inf")
874
- ):
875
- console.logger.error(f"{context}: quantity cannot be NaN or infinite")
876
- return
877
-
878
- if quantity <= 0:
879
- console.logger.error(
880
- f"{context}: quantity must be positive, got {quantity}"
881
- )
882
-
883
- if quantity > 1e9:
884
- console.logger.error(f"{context}: quantity too large: {quantity}")
885
-
886
- @staticmethod
887
- def timezone_aware(
888
- timestamp: pd.Timestamp | None, field_name: str, context: str = ""
889
- ) -> None:
890
- """Validate that timestamp is timezone-aware and log errors."""
891
- if timestamp is not None and timestamp.tz is None:
892
- console.logger.error(
893
- f"{context}: {field_name} must be timezone-aware, got {timestamp}"
894
- )