onesecondtrader 0.7.0__py3-none-any.whl → 0.8.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.
File without changes
@@ -0,0 +1,133 @@
1
+ from dataclasses import dataclass
2
+ import enum
3
+
4
+
5
+ class BrokerType(enum.Enum):
6
+ """
7
+ Enum for broker types.
8
+
9
+ **Attributes:**
10
+
11
+ | Enum | Value | Description |
12
+ |------|-------|-------------|
13
+ | `LOCAL_SIMULATED` | `enum.auto()` | Locally simulated broker |
14
+ | `IB_SIMULATED` | `enum.auto()` | Interactive Brokers paper trading account |
15
+ | `IB_LIVE` | `enum.auto()` | Interactive Brokers live trading account |
16
+ | `MT5` | `enum.auto()` | MetaTrader 5 |
17
+ """
18
+
19
+ LOCAL_SIMULATED = enum.auto()
20
+ IB_SIMULATED = enum.auto()
21
+ IB_LIVE = enum.auto()
22
+ MT5 = enum.auto()
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class Bar:
27
+ """
28
+ Class for representing a OHLC(V) bar of market data.
29
+
30
+ Attributes:
31
+ open (float): Open price
32
+ high (float): High price
33
+ low (float): Low price
34
+ close (float): Close price
35
+ volume (float): Volume
36
+ """
37
+
38
+ open: float
39
+ high: float
40
+ low: float
41
+ close: float
42
+ volume: float | None = None
43
+
44
+
45
+ class Side(enum.Enum):
46
+ """
47
+ Enum for order sides.
48
+ """
49
+
50
+ BUY = enum.auto()
51
+ SELL = enum.auto()
52
+
53
+
54
+ class TimeInForce(enum.Enum):
55
+ """
56
+ Order time-in-force specifications.
57
+
58
+ **Attributes:**
59
+
60
+ | Enum | Value | Description |
61
+ |------|-------|-------------|
62
+ | `DAY` | `enum.auto()` | Valid until end of trading day |
63
+ | `FOK` | `enum.auto()` | Fill entire order immediately or cancel (Fill-or-Kill) |
64
+ | `GTC` | `enum.auto()` | Active until explicitly cancelled (Good-Till-Cancelled) |
65
+ | `GTD` | `enum.auto()` | Active until specified date (Good-Till-Date) |
66
+ | `IOC` | `enum.auto()` | Execute available quantity immediately, cancel rest
67
+ (Immediate-or-Cancel) |
68
+ """
69
+
70
+ DAY = enum.auto()
71
+ FOK = enum.auto()
72
+ GTC = enum.auto()
73
+ GTD = enum.auto()
74
+ IOC = enum.auto()
75
+
76
+
77
+ class OrderType(enum.Enum):
78
+ """
79
+ Enum for order types.
80
+
81
+ **Attributes:**
82
+
83
+ | Enum | Value | Description |
84
+ |------|-------|-------------|
85
+ | `MARKET` | `enum.auto()` | Market order |
86
+ | `LIMIT` | `enum.auto()` | Limit order |
87
+ | `STOP` | `enum.auto()` | Stop order |
88
+ | `STOP_LIMIT` | `enum.auto()` | Stop-limit order |
89
+ """
90
+
91
+ MARKET = enum.auto()
92
+ LIMIT = enum.auto()
93
+ STOP = enum.auto()
94
+ STOP_LIMIT = enum.auto()
95
+
96
+
97
+ class OrderLifecycleState(enum.Enum):
98
+ """
99
+ Enum for order lifecycle states.
100
+
101
+ **Attributes:**
102
+
103
+ | Enum | Value | Description |
104
+ |------|-------|-------------|
105
+ | `PENDING` | `enum.auto()` | Order has been submitted, but not yet acknowledged by
106
+ the broker |
107
+ | `OPEN` | `enum.auto()` | Order has been acknowledged by the broker, but not yet
108
+ filled or cancelled |
109
+ | `FILLED` | `enum.auto()` | Order has been filled |
110
+ | `CANCELLED` | `enum.auto()` | Order has been cancelled |
111
+ """
112
+
113
+ PENDING = enum.auto()
114
+ OPEN = enum.auto()
115
+ PARTIALLY_FILLED = enum.auto()
116
+ FILLED = enum.auto()
117
+ CANCELLED = enum.auto()
118
+
119
+
120
+ class OrderRejectionReason(enum.Enum):
121
+ """
122
+ Enum for order rejection reasons.
123
+
124
+ **Attributes:**
125
+
126
+ | Enum | Value | Description |
127
+ |------|-------|-------------|
128
+ | `UNKNOWN` | `enum.auto()` | Unknown reason |
129
+ | `NEGATIVE_QUANTITY` | `enum.auto()` | Negative quantity |
130
+ """
131
+
132
+ UNKNOWN = enum.auto()
133
+ NEGATIVE_QUANTITY = enum.auto()
File without changes
File without changes
@@ -0,0 +1,742 @@
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
+
27
+ R --> R1
28
+ R --> R2
29
+ R --> R3
30
+ R --> R4
31
+
32
+ R2 --> R21
33
+ R2 --> R22
34
+
35
+ A1[events.Market.IncomingBar]
36
+
37
+ R1 --> A1
38
+
39
+ style A1 fill:#6F42C1,fill-opacity:0.3
40
+
41
+ B1[events.Request.MarketOrder]
42
+ B2[events.Request.LimitOrder]
43
+ B3[events.Request.StopOrder]
44
+ B4[events.Request.StopLimitOrder]
45
+ B5[events.Request.CancelOrder]
46
+ B6[events.Request.FlushSymbol]
47
+ B7[events.Request.FlushAll]
48
+
49
+ R21 --> B1
50
+ R21 --> B2
51
+ R21 --> B3
52
+ R21 --> B4
53
+ R22 --> B5
54
+ R22 --> B6
55
+ R22 --> B7
56
+
57
+ style B1 fill:#6F42C1,fill-opacity:0.3
58
+ style B2 fill:#6F42C1,fill-opacity:0.3
59
+ style B3 fill:#6F42C1,fill-opacity:0.3
60
+ style B4 fill:#6F42C1,fill-opacity:0.3
61
+ style B5 fill:#6F42C1,fill-opacity:0.3
62
+ style B6 fill:#6F42C1,fill-opacity:0.3
63
+ style B7 fill:#6F42C1,fill-opacity:0.3
64
+
65
+ C1[events.Response.OrderSubmitted]
66
+ C2[events.Response.OrderFilled]
67
+ C3[events.Response.OrderCancelled]
68
+ C4[events.Response.OrderRejected]
69
+
70
+ R3 --> C1
71
+ R3 --> C2
72
+ R3 --> C3
73
+ R3 --> C4
74
+
75
+ style C1 fill:#6F42C1,fill-opacity:0.3
76
+ style C2 fill:#6F42C1,fill-opacity:0.3
77
+ style C3 fill:#6F42C1,fill-opacity:0.3
78
+ style C4 fill:#6F42C1,fill-opacity:0.3
79
+
80
+ D1[events.System.Shutdown]
81
+
82
+ R4 --> D1
83
+
84
+ style D1 fill:#6F42C1,fill-opacity:0.3
85
+
86
+
87
+ subgraph Market ["Market Update Event Messages"]
88
+ R1
89
+ A1
90
+
91
+ subgraph MarketNamespace ["events.Market Namespace"]
92
+ A1
93
+ end
94
+
95
+ end
96
+
97
+
98
+ subgraph Request ["Broker Request Event Messages"]
99
+ R2
100
+ R21
101
+ R22
102
+ B1
103
+ B2
104
+ B3
105
+ B4
106
+ B5
107
+ B6
108
+ B7
109
+
110
+ subgraph RequestNamespace ["events.Request Namespace"]
111
+ B1
112
+ B2
113
+ B3
114
+ B4
115
+ B5
116
+ B6
117
+ B7
118
+ end
119
+
120
+ end
121
+
122
+ subgraph Response ["Broker Response Event Messages"]
123
+ R3
124
+ C1
125
+ C2
126
+ C3
127
+ C4
128
+
129
+ subgraph ResponseNamespace ["events.Response Namespace"]
130
+ C1
131
+ C2
132
+ C3
133
+ C4
134
+ end
135
+
136
+ end
137
+
138
+ subgraph System ["System-Internal Event Messages"]
139
+ R4
140
+ D1
141
+
142
+ subgraph SystemNamespace ["events.System Namespace"]
143
+ D1
144
+ end
145
+
146
+ end
147
+ ```
148
+ """
149
+
150
+ import dataclasses
151
+ import pandas as pd
152
+ import re
153
+ import uuid
154
+ from onesecondtrader.core import models
155
+ from onesecondtrader.monitoring import console
156
+
157
+
158
+ class Base:
159
+ """
160
+ Namespace for event base dataclasses.
161
+ """
162
+
163
+ @dataclasses.dataclass(kw_only=True, frozen=True)
164
+ class Event:
165
+ """
166
+ Base event message dataclass.
167
+ This dataclass cannot be instantiated directly.
168
+
169
+ Attributes:
170
+ ts_event (pd.Timestamp): Timestamp of the event in pandas Timestamp format.
171
+ (Must be timezone-aware.)
172
+ event_bus_sequence_number (int | None): Auto-generated Sequence number of
173
+ the event.
174
+ This will be assigned as soon as the event enters the event bus via
175
+ `messaging.EventBus.publish(<event>)`.
176
+ """
177
+
178
+ ts_event: pd.Timestamp
179
+ event_bus_sequence_number: int | None = dataclasses.field(
180
+ default=None, init=False
181
+ )
182
+
183
+ def __new__(cls, *args, **kwargs):
184
+ if cls is Base.Event:
185
+ console.logger.error(
186
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
187
+ )
188
+ return super().__new__(cls)
189
+
190
+ def __post_init__(self) -> None:
191
+ _Validate.timezone_aware(self.ts_event, "ts_event", "Event")
192
+
193
+ @dataclasses.dataclass(kw_only=True, frozen=True)
194
+ class Market(Event):
195
+ """
196
+ Base event message dataclass for market events.
197
+ Inherits from `Base.Event`.
198
+ Each market event message is associated with a specific financial instrument via
199
+ the `symbol` field.
200
+ This dataclass cannot be instantiated directly.
201
+
202
+ Attributes:
203
+ symbol (str): Symbol of the financial instrument.
204
+ """
205
+
206
+ symbol: str
207
+
208
+ def __new__(cls, *args, **kwargs):
209
+ if cls is Base.Market:
210
+ console.logger.error(
211
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
212
+ )
213
+ return super().__new__(cls)
214
+
215
+ def __post_init__(self) -> None:
216
+ super().__post_init__()
217
+ _Validate.symbol(self.symbol, "Market event")
218
+
219
+ @dataclasses.dataclass(kw_only=True, frozen=True)
220
+ class Request(Event):
221
+ """
222
+ Base event message dataclass for broker requests.
223
+ This dataclass cannot be instantiated directly.
224
+ `ts_event` is auto-generated by default.
225
+
226
+ Attributes:
227
+ ts_event: Timestamp of the event. (defaults to current UTC time;
228
+ auto-generated)
229
+ """
230
+
231
+ ts_event: pd.Timestamp = dataclasses.field(
232
+ default_factory=lambda: pd.Timestamp.now(tz="UTC")
233
+ )
234
+
235
+ def __new__(cls, *args, **kwargs):
236
+ if cls is Base.Request:
237
+ console.logger.error(
238
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
239
+ )
240
+ return super().__new__(cls)
241
+
242
+ @dataclasses.dataclass(kw_only=True, frozen=True)
243
+ class OrderRequest(Request):
244
+ """
245
+ Base event message dataclass for order requests.
246
+ Inherits from `Base.Request`.
247
+ This dataclass cannot be instantiated directly.
248
+
249
+ Attributes:
250
+ symbol (str): Symbol of the financial instrument.
251
+ side (models.Side): Side of the order.
252
+ quantity (float): Quantity of the order.
253
+ time_in_force (models.TimeInForce): Time in force of the order.
254
+ order_expiration (pd.Timestamp | None): Expiration timestamp of the order
255
+ (optional).
256
+ Only relevant if `time_in_force` is `models.TimeInForce.GTD`.
257
+ order_id (uuid.UUID): Unique ID of the order. (auto-generated)
258
+ """
259
+
260
+ symbol: str
261
+ side: models.Side
262
+ quantity: float
263
+ time_in_force: models.TimeInForce
264
+ order_expiration: pd.Timestamp | None = None
265
+ order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
266
+
267
+ def __new__(cls, *args, **kwargs):
268
+ if cls is Base.OrderRequest:
269
+ console.logger.error(
270
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
271
+ )
272
+ return super().__new__(cls)
273
+
274
+ def __post_init__(self) -> None:
275
+ super().__post_init__()
276
+ _Validate.symbol(self.symbol, f"Order {self.order_id}")
277
+
278
+ _Validate.timezone_aware(
279
+ self.order_expiration, "order_expiration", f"Order {self.order_id}"
280
+ )
281
+ _Validate.quantity(self.quantity, f"Order {self.order_id}")
282
+
283
+ if self.time_in_force.value == 4:
284
+ if self.order_expiration is None:
285
+ console.logger.error(
286
+ f"Order {self.order_id}: GTD order missing expiration "
287
+ f"timestamp."
288
+ )
289
+ elif self.order_expiration <= self.ts_event:
290
+ console.logger.error(
291
+ f"Order {self.order_id}: GTD expiration "
292
+ f"{self.order_expiration} "
293
+ f"is not after event timestamp {self.ts_event}."
294
+ )
295
+
296
+ @dataclasses.dataclass(kw_only=True, frozen=True)
297
+ class CancelRequest(Request):
298
+ """
299
+ Base event message dataclass for order cancellation requests.
300
+ Inherits from `Base.Request`.
301
+ This dataclass cannot be instantiated directly.
302
+ """
303
+
304
+ def __new__(cls, *args, **kwargs):
305
+ if cls is Base.CancelRequest:
306
+ console.logger.error(
307
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
308
+ )
309
+ return super().__new__(cls)
310
+
311
+ @dataclasses.dataclass(kw_only=True, frozen=True)
312
+ class Response(Event):
313
+ """
314
+ Base event message dataclass for broker responses.
315
+ This dataclass cannot be instantiated directly.
316
+ """
317
+
318
+ def __new__(cls, *args, **kwargs):
319
+ if cls is Base.Response:
320
+ console.logger.error(
321
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
322
+ )
323
+ return super().__new__(cls)
324
+
325
+ @dataclasses.dataclass(kw_only=True, frozen=True)
326
+ class System(Event):
327
+ """
328
+ Base event message dataclass for system-internal messages.
329
+ This dataclass cannot be instantiated directly.
330
+ `ts_event` is auto-generated by default.
331
+
332
+ Attributes:
333
+ ts_event: Timestamp of the event. (defaults to current UTC time;
334
+ auto-generated)
335
+ """
336
+
337
+ ts_event: pd.Timestamp = dataclasses.field(
338
+ default_factory=lambda: pd.Timestamp.now(tz="UTC")
339
+ )
340
+
341
+ def __new__(cls, *args, **kwargs):
342
+ if cls is Base.System:
343
+ console.logger.error(
344
+ f"Cannot instantiate abstract class '{cls.__name__}' directly"
345
+ )
346
+ return super().__new__(cls)
347
+
348
+
349
+ class Market:
350
+ """
351
+ Namespace for market update event messages.
352
+ """
353
+
354
+ @dataclasses.dataclass(kw_only=True, frozen=True)
355
+ class IncomingBar(Base.Market):
356
+ """
357
+ Event message dataclass for incoming market data bars.
358
+ Inherits from `Base.Market`.
359
+
360
+ Attributes:
361
+ bar (models.Bar): Bar of market data.
362
+
363
+ Examples:
364
+ >>> from onesecondtrader.messaging import events
365
+ >>> from onesecondtrader.core import models
366
+ >>> event = events.Market.IncomingBar(
367
+ ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
368
+ ... symbol="AAPL",
369
+ ... bar=models.Bar(
370
+ ... open=100.0,
371
+ ... high=101.0,
372
+ ... low=99.0,
373
+ ... close=100.5,
374
+ ... volume=10000,
375
+ ... ),
376
+ ... )
377
+ ```
378
+ """
379
+
380
+ bar: models.Bar
381
+
382
+
383
+ class Request:
384
+ """
385
+ Namespace for broker request event messages.
386
+ """
387
+
388
+ @dataclasses.dataclass(kw_only=True, frozen=True)
389
+ class MarketOrder(Base.OrderRequest):
390
+ """
391
+ Event message dataclass for submitting a market order to the broker.
392
+
393
+ Attributes:
394
+ order_type (models.OrderType): Type of the order (automatically set to
395
+ models.OrderType.MARKET).
396
+
397
+ Examples:
398
+ >>> from onesecondtrader.messaging import events
399
+ >>> event = events.Request.MarketOrder(
400
+ ... symbol="AAPL",
401
+ ... side=models.Side.BUY,
402
+ ... quantity=100.0,
403
+ ... time_in_force=models.TimeInForce.DAY,
404
+ ... )
405
+ """
406
+
407
+ order_type: models.OrderType = dataclasses.field(
408
+ init=False, default=models.OrderType.MARKET
409
+ )
410
+
411
+ @dataclasses.dataclass(kw_only=True, frozen=True)
412
+ class LimitOrder(Base.OrderRequest):
413
+ """
414
+ Event message dataclass for submitting a limit order to the broker.
415
+
416
+ Attributes:
417
+ order_type (models.OrderType): Type of the order (automatically set to
418
+ models.OrderType.LIMIT).
419
+ limit_price (float): Limit price of the order.
420
+
421
+ Examples:
422
+ >>> from onesecondtrader.messaging import events
423
+ >>> event = events.Request.LimitOrder(
424
+ ... symbol="AAPL",
425
+ ... side=models.Side.BUY,
426
+ ... quantity=100.0,
427
+ ... time_in_force=models.TimeInForce.DAY,
428
+ ... limit_price=100.0,
429
+ ... )
430
+ """
431
+
432
+ order_type: models.OrderType = dataclasses.field(
433
+ init=False, default=models.OrderType.LIMIT
434
+ )
435
+ limit_price: float
436
+
437
+ @dataclasses.dataclass(kw_only=True, frozen=True)
438
+ class StopOrder(Base.OrderRequest):
439
+ """
440
+ Event message dataclass for submitting a stop order to the broker.
441
+
442
+ Attributes:
443
+ order_type (models.OrderType): Type of the order (automatically set to
444
+ models.OrderType.STOP).
445
+ stop_price (float): Stop price of the order.
446
+
447
+ Examples:
448
+ >>> from onesecondtrader.messaging import events
449
+ >>> event = events.Request.StopOrder(
450
+ ... symbol="AAPL",
451
+ ... side=models.Side.BUY,
452
+ ... quantity=100.0,
453
+ ... time_in_force=models.TimeInForce.DAY,
454
+ ... stop_price=100.0,
455
+ ... )
456
+ """
457
+
458
+ order_type: models.OrderType = dataclasses.field(
459
+ init=False, default=models.OrderType.STOP
460
+ )
461
+ stop_price: float
462
+
463
+ @dataclasses.dataclass(kw_only=True, frozen=True)
464
+ class StopLimitOrder(Base.OrderRequest):
465
+ """
466
+ Event message dataclass for submitting a stop-limit order to the broker.
467
+
468
+ Attributes:
469
+ order_type (models.OrderType): Type of the order (automatically set to
470
+ models.OrderType.STOP_LIMIT).
471
+ stop_price (float): Stop price of the order.
472
+ limit_price (float): Limit price of the order.
473
+
474
+ Examples:
475
+ >>> from onesecondtrader.messaging import events
476
+ >>> event = events.Request.StopLimitOrder(
477
+ ... symbol="AAPL",
478
+ ... side=models.Side.BUY,
479
+ ... quantity=100.0,
480
+ ... time_in_force=models.TimeInForce.DAY,
481
+ ... stop_price=100.0,
482
+ ... limit_price=100.0,
483
+ ... )
484
+ """
485
+
486
+ order_type: models.OrderType = dataclasses.field(
487
+ init=False, default=models.OrderType.STOP_LIMIT
488
+ )
489
+ stop_price: float
490
+ limit_price: float
491
+
492
+ @dataclasses.dataclass(kw_only=True, frozen=True)
493
+ class CancelOrder(Base.CancelRequest):
494
+ """
495
+ Event message dataclass for cancelling an order.
496
+
497
+ Attributes:
498
+ order_id (uuid.UUID): Unique ID of the order to cancel.
499
+
500
+ Examples:
501
+ >>> from onesecondtrader.messaging import events
502
+ >>> event = events.Request.CancelOrder(
503
+ ... order_id=uuid.UUID("12345678-1234-5678-1234-567812345678"),
504
+ ... )
505
+ """
506
+
507
+ order_id: uuid.UUID
508
+
509
+ @dataclasses.dataclass(kw_only=True, frozen=True)
510
+ class FlushSymbol(Base.Request):
511
+ """
512
+ Event message dataclass for flushing all orders for a symbol.
513
+
514
+ Attributes:
515
+ symbol (str): Symbol to flush.
516
+
517
+ Examples:
518
+ >>> from onesecondtrader.messaging import events
519
+ >>> event = events.Request.FlushSymbol(
520
+ ... symbol="AAPL",
521
+ ... )
522
+ """
523
+
524
+ symbol: str
525
+
526
+ def __post_init__(self) -> None:
527
+ super().__post_init__()
528
+ _Validate.symbol(self.symbol, "Flush request")
529
+
530
+ @dataclasses.dataclass(kw_only=True, frozen=True)
531
+ class FlushAll(Base.Request):
532
+ """
533
+ Event message dataclass for flushing all orders for all symbols.
534
+
535
+ Examples:
536
+ >>> from onesecondtrader.messaging import events
537
+ >>> event = events.Request.FlushAll()
538
+ """
539
+
540
+ pass
541
+
542
+
543
+ class Response:
544
+ """
545
+ Namespace for broker response event messages.
546
+ """
547
+
548
+ @dataclasses.dataclass(kw_only=True, frozen=True)
549
+ class OrderSubmitted(Base.Response):
550
+ """
551
+ Event message dataclass for order submission confirmation from the broker.
552
+
553
+ Attributes:
554
+ order_submitted_id (uuid.UUID): Unique ID of the submitted order.
555
+ associated_request_id (uuid.UUID): Unique ID of the request that triggered
556
+ the order submission.
557
+
558
+ Examples:
559
+ >>> from onesecondtrader.messaging import events
560
+ >>> import pandas as pd
561
+ >>> import uuid
562
+ >>> event = events.Response.OrderSubmitted(
563
+ ... ts_event=pd.Timestamp(
564
+ ... "2023-01-01 00:00:00", tz="UTC"),
565
+ ... order_submitted_id=uuid.UUID(
566
+ ... "12345678-1234-5678-1234-567812345678"),
567
+ ... associated_request_id=uuid.UUID(
568
+ ... "12345678-1234-5678-1234-567812345678"
569
+ ... ),
570
+ ... )
571
+ """
572
+
573
+ order_submitted_id: uuid.UUID
574
+ associated_request_id: uuid.UUID
575
+
576
+ @dataclasses.dataclass(kw_only=True, frozen=True)
577
+ class OrderFilled(Base.Response):
578
+ """
579
+ Event message dataclass for order fill confirmation from the broker.
580
+
581
+ Attributes:
582
+ fill_id (uuid.UUID): Unique ID of the fill. (auto-generated)
583
+ associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
584
+ that triggered the fill.
585
+ side (models.Side): Side of the fill.
586
+ quantity_filled (float): Quantity filled.
587
+ filled_at_price (float): Price at which the fill was executed.
588
+ commission_and_fees (float): Commission and fees for the fill.
589
+ net_fill_value (float): Net fill value (auto-generated).
590
+ exchange (str | None): Exchange on which the fill was executed. (optional;
591
+ defaults to "SIMULATED")
592
+
593
+ Examples:
594
+ >>> from onesecondtrader.messaging import events
595
+ >>> from onesecondtrader.core import models
596
+ >>> import pandas as pd
597
+ >>> import uuid
598
+ >>> event = events.Response.OrderFilled(
599
+ ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
600
+ ... associated_order_submitted_id=uuid.UUID(
601
+ ... "12345678-1234-5678-1234-567812345678"
602
+ ... ),
603
+ ... side=models.Side.BUY,
604
+ ... quantity_filled=100.0,
605
+ ... filled_at_price=100.0,
606
+ ... commission_and_fees=1.0,
607
+ ... )
608
+ """
609
+
610
+ fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
611
+ associated_order_submitted_id: uuid.UUID
612
+ side: models.Side
613
+ quantity_filled: float
614
+ filled_at_price: float
615
+ commission_and_fees: float
616
+ net_fill_value: float = dataclasses.field(init=False)
617
+ exchange: str | None = None
618
+
619
+ def __post_init__(self):
620
+ object.__setattr__(self, "fill_id", self.fill_id or uuid.uuid4())
621
+
622
+ gross_value = self.filled_at_price * self.quantity_filled
623
+
624
+ if self.side.value == 1:
625
+ net_value = gross_value + self.commission_and_fees
626
+ else:
627
+ net_value = gross_value - self.commission_and_fees
628
+
629
+ object.__setattr__(self, "net_fill_value", net_value)
630
+
631
+ object.__setattr__(self, "exchange", self.exchange or "SIMULATED")
632
+
633
+ @dataclasses.dataclass(kw_only=True, frozen=True)
634
+ class OrderCancelled(Base.Response):
635
+ """
636
+ Event message dataclass for order cancellation confirmation from the broker.
637
+
638
+ Attributes:
639
+ associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
640
+ that was cancelled.
641
+
642
+ Examples:
643
+ >>> from onesecondtrader.messaging import events
644
+ >>> import pandas as pd
645
+ >>> import uuid
646
+ >>> event = events.Response.OrderCancelled(
647
+ ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
648
+ ... associated_order_submitted_id=uuid.UUID(
649
+ ... "12345678-1234-5678-1234-567812345678"
650
+ ... ),
651
+ ... )
652
+ """
653
+
654
+ associated_order_submitted_id: uuid.UUID
655
+
656
+ @dataclasses.dataclass(kw_only=True, frozen=True)
657
+ class OrderRejected(Base.Response):
658
+ """
659
+ Event message dataclass for order rejection confirmation from the broker.
660
+
661
+ Attributes:
662
+ associated_order_submitted_id (uuid.UUID): Unique ID of the submitted order
663
+ that was rejected.
664
+ reason (models.OrderRejectionReason): Reason for the rejection.
665
+
666
+ Examples:
667
+ >>> from onesecondtrader.messaging import events
668
+ >>> from onesecondtrader.core import models
669
+ >>> import pandas as pd
670
+ >>> import uuid
671
+ >>> event = events.Response.OrderRejected(
672
+ ... ts_event=pd.Timestamp("2023-01-01 00:00:00", tz="UTC"),
673
+ ... associated_order_submitted_id=uuid.UUID(
674
+ ... "12345678-1234-5678-1234-567812345678"
675
+ ... ),
676
+ ... reason=models.OrderRejectionReason.NEGATIVE_QUANTITY,
677
+ ... )
678
+ """
679
+
680
+ associated_order_submitted_id: uuid.UUID
681
+ reason: models.OrderRejectionReason
682
+
683
+
684
+ class System:
685
+ """
686
+ Namespace for system-internal event messages.
687
+ """
688
+
689
+ @dataclasses.dataclass(kw_only=True, frozen=True)
690
+ class Shutdown(Base.System):
691
+ """
692
+ Event message dataclass for system shutdown.
693
+
694
+ Examples:
695
+ >>> from onesecondtrader.messaging import events
696
+ >>> event = events.System.Shutdown()
697
+ """
698
+
699
+ pass
700
+
701
+
702
+ class _Validate:
703
+ """Internal validation utilities for events."""
704
+
705
+ @staticmethod
706
+ def symbol(symbol: str, context: str = "") -> None:
707
+ """Validate symbol format and log errors."""
708
+ if not symbol or not symbol.strip():
709
+ console.logger.error(f"{context}: Symbol cannot be empty or whitespace")
710
+ return
711
+
712
+ if not re.fullmatch(r"[A-Z0-9._-]+", symbol):
713
+ console.logger.error(f"{context}: Invalid symbol format: {symbol}")
714
+
715
+ @staticmethod
716
+ def quantity(quantity: float, context: str = "") -> None:
717
+ """Validate quantity values and log errors."""
718
+ if (
719
+ quantity != quantity
720
+ or quantity == float("inf")
721
+ or quantity == float("-inf")
722
+ ):
723
+ console.logger.error(f"{context}: quantity cannot be NaN or infinite")
724
+ return
725
+
726
+ if quantity <= 0:
727
+ console.logger.error(
728
+ f"{context}: quantity must be positive, got {quantity}"
729
+ )
730
+
731
+ if quantity > 1e9:
732
+ console.logger.error(f"{context}: quantity too large: {quantity}")
733
+
734
+ @staticmethod
735
+ def timezone_aware(
736
+ timestamp: pd.Timestamp | None, field_name: str, context: str = ""
737
+ ) -> None:
738
+ """Validate that timestamp is timezone-aware and log errors."""
739
+ if timestamp is not None and timestamp.tz is None:
740
+ console.logger.error(
741
+ f"{context}: {field_name} must be timezone-aware, got {timestamp}"
742
+ )
@@ -5,6 +5,7 @@ Simple console logging configuration for terminal output.
5
5
 
6
6
  import logging
7
7
 
8
+
8
9
  logging.basicConfig(
9
10
  level=logging.DEBUG,
10
11
  format="%(asctime)s - %(levelname)s - %(threadName)s - %(message)s",
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: onesecondtrader
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place.
5
5
  Author: Nils P. Kujath
6
6
  Author-email: 63961429+NilsKujath@users.noreply.github.com
@@ -0,0 +1,14 @@
1
+ onesecondtrader/__init__.py,sha256=TNqlT20sH46-J7F6giBxwWYG1-wFZZt7toDbZeQK6KQ,210
2
+ onesecondtrader/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ onesecondtrader/core/models.py,sha256=Pw3og5b2mzcoPpMfv3ZdKPvUvhLvh_eT68WJSlIpRyo,3325
4
+ onesecondtrader/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ onesecondtrader/messaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ onesecondtrader/messaging/events.py,sha256=eaWXQQIUnRNOR-9n5-6lyLbZ6bUtzjD4GI567U_vh4g,23625
7
+ onesecondtrader/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ onesecondtrader/monitoring/console.py,sha256=1mrojXkyL4ro7ebkvDMGNQiCL-93WEylRuwnfmEKzVs,299
9
+ onesecondtrader/monitoring/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ onesecondtrader/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ onesecondtrader-0.8.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
+ onesecondtrader-0.8.0.dist-info/METADATA,sha256=ry6U-Hj4bxnaKgAWXdlM7CtL0Zv0w3_kri_NE2T-TzA,9518
13
+ onesecondtrader-0.8.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
14
+ onesecondtrader-0.8.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- onesecondtrader/__init__.py,sha256=TNqlT20sH46-J7F6giBxwWYG1-wFZZt7toDbZeQK6KQ,210
2
- onesecondtrader/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- onesecondtrader/monitoring/console.py,sha256=9_WrLnfG_IanqBX3FJQOy7ZYdceoTAXb5BXFYJTUq0o,298
4
- onesecondtrader-0.7.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
5
- onesecondtrader-0.7.0.dist-info/METADATA,sha256=WXbgynzRnwqWMZerSQidFXiEtw5rZCWEiEw4XCjQn7E,9518
6
- onesecondtrader-0.7.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
7
- onesecondtrader-0.7.0.dist-info/RECORD,,