onesecondtrader 0.6.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.
- onesecondtrader/__init__.py +1 -3
- onesecondtrader/core/__init__.py +0 -0
- onesecondtrader/core/models.py +133 -0
- onesecondtrader/core/py.typed +0 -0
- onesecondtrader/messaging/__init__.py +0 -0
- onesecondtrader/messaging/events.py +742 -0
- onesecondtrader/monitoring/__init__.py +0 -0
- onesecondtrader/{monitoring.py → monitoring/console.py} +2 -3
- onesecondtrader/monitoring/py.typed +0 -0
- onesecondtrader/py.typed +0 -0
- onesecondtrader-0.8.0.dist-info/METADATA +248 -0
- onesecondtrader-0.8.0.dist-info/RECORD +14 -0
- onesecondtrader-0.6.0.dist-info/METADATA +0 -24
- onesecondtrader-0.6.0.dist-info/RECORD +0 -6
- {onesecondtrader-0.6.0.dist-info → onesecondtrader-0.8.0.dist-info}/LICENSE +0 -0
- {onesecondtrader-0.6.0.dist-info → onesecondtrader-0.8.0.dist-info}/WHEEL +0 -0
onesecondtrader/__init__.py
CHANGED
|
@@ -4,11 +4,9 @@ The Trading Infrastructure Toolkit for Python.
|
|
|
4
4
|
Research, simulate, and deploy algorithmic trading strategies — all in one place.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from .monitoring import logger
|
|
7
|
+
from .monitoring.console import logger
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
12
|
-
# Core infrastructure
|
|
13
11
|
"logger",
|
|
14
12
|
]
|
|
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
|
+
)
|
|
File without changes
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Console logging utilities for OneSecondTrader.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
a logger instance for use throughout the package.
|
|
3
|
+
Simple console logging configuration for terminal output.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
import logging
|
|
File without changes
|
onesecondtrader/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: onesecondtrader
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place.
|
|
5
|
+
Author: Nils P. Kujath
|
|
6
|
+
Author-email: 63961429+NilsKujath@users.noreply.github.com
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: pandas (>=2.3.1,<3.0.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# OneSecondTrader
|
|
16
|
+
|
|
17
|
+
[](https://github.com/nilskujath/onesecondtrader/actions/workflows/release.yml)
|
|
18
|
+
[](https://www.onesecondtrader.com)
|
|
19
|
+
[](https://pypi.org/project/onesecondtrader/)
|
|
20
|
+
[](https://github.com/nilskujath/onesecondtrader/blob/master/LICENSE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
For documentation, please visit [onesecondtrader.com](https://www.onesecondtrader.com).
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## For Developers: Continuous Integration & Delivery (CI/CD) Pipeline
|
|
29
|
+
|
|
30
|
+
This project's continuous integration & continuous delivery (CI/CD) pipeline consists of two distinct workflows:
|
|
31
|
+
**local pre-commit hooks** that run on `git commit` to ensure code quality,
|
|
32
|
+
and **GitHub Actions** that run on `git push origin master` to automate releases.
|
|
33
|
+
|
|
34
|
+
In order for the pipeline to work, the following configuration is required:
|
|
35
|
+
|
|
36
|
+
* version field in `pyproject.toml` must be set to appropriate version
|
|
37
|
+
```toml
|
|
38
|
+
[tool.poetry]
|
|
39
|
+
name = "onesecondtrader"
|
|
40
|
+
version = "0.1.0" # Updated automatically by bump_version.py
|
|
41
|
+
```
|
|
42
|
+
* `mkdocs.yml` must have `mkdocstrings-python` plugin configured
|
|
43
|
+
```yaml
|
|
44
|
+
plugins:
|
|
45
|
+
- mkdocstrings:
|
|
46
|
+
handlers:
|
|
47
|
+
python:
|
|
48
|
+
options:
|
|
49
|
+
docstring_style: google
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Local Pre-Commit Workflow
|
|
53
|
+
|
|
54
|
+
To ensure that only good quality code is commited to the repository, a series of pre-commit hooks are executed before each commit.
|
|
55
|
+
These hooks include code quality checks, testing, security scans, and automated API reference generation.
|
|
56
|
+
This workflow is orchestrated by the `pre-commit` package, which is configured in the `.pre-commit-config.yaml` file.
|
|
57
|
+
If any of these checks fail, the commit is blocked and the developer must fix the issues before retrying.
|
|
58
|
+
|
|
59
|
+
Prior to usage, the pre-commit hooks must be installed by running:
|
|
60
|
+
```bash
|
|
61
|
+
poetry run pre-commit install
|
|
62
|
+
poetry run pre-commit install --hook-type commit-msg
|
|
63
|
+
poetry run pre-commit run --all-files # Optional: Test installation
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages.
|
|
67
|
+
This standardized format enables automated semantic versioning and changelog generation.
|
|
68
|
+
The commit messages must have the following format:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
<type>: <description>
|
|
72
|
+
|
|
73
|
+
[optional body]
|
|
74
|
+
|
|
75
|
+
[optional footer(s)]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The commit message must start with a type, followed by a colon and a space, and then a description. The type must be one of the following:
|
|
79
|
+
|
|
80
|
+
- **feat**: New features that add functionality
|
|
81
|
+
- **fix**: Bug fixes and patches
|
|
82
|
+
- **docs**: Documentation changes only
|
|
83
|
+
- **chore**: Maintenance tasks, dependency updates, build changes
|
|
84
|
+
- **test**: Adding or modifying tests
|
|
85
|
+
- **refactor**: Code changes that neither fix bugs nor add features
|
|
86
|
+
- **perf**: Performance improvements
|
|
87
|
+
- **ci**: Changes to CI/CD configuration
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
feat: added trade-by-trade chart generation
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The following diagram illustrates this pre-commit workflow:
|
|
96
|
+
|
|
97
|
+
```mermaid
|
|
98
|
+
---
|
|
99
|
+
config:
|
|
100
|
+
themeVariables:
|
|
101
|
+
fontSize: "11px"
|
|
102
|
+
---
|
|
103
|
+
graph TD
|
|
104
|
+
A([<kbd>git commit</kbd>]) -->|Trigger Pre-commit Workflow on <kbd>commit</kbd>| PrecommitHooks
|
|
105
|
+
|
|
106
|
+
subgraph PrecommitHooks ["Local Pre-commit Hooks"]
|
|
107
|
+
B["<b>Code Quality Checks</b><br/>• Ruff Check & Format<br/>• MyPy Type Checking<br/>• Tests & Doctests"]
|
|
108
|
+
C["<b>Security Checks</b><br/>• Gitleaks Secret Detection"]
|
|
109
|
+
D["<b>File Validation</b><br/>• YAML/TOML/JSON Check<br/>• End-of-file Fixer<br/>• Large Files Check<br/>• Merge Conflict Check<br/>• Debug Statements Check"]
|
|
110
|
+
E["<b>Generate API Documentation</b> via <kbd>scripts/generate_api_docs.py</kbd><br/>• Auto-generate docs<br/>• Stage changes"]
|
|
111
|
+
end
|
|
112
|
+
B --> C --> D --> E
|
|
113
|
+
|
|
114
|
+
F([Write Commit Message])
|
|
115
|
+
PrecommitHooks -->|Pass| F
|
|
116
|
+
PrecommitHooks -.->|Fail| H
|
|
117
|
+
|
|
118
|
+
subgraph CommitMessageHook ["Commit Message Hook"]
|
|
119
|
+
G{Commit Message Valid?}
|
|
120
|
+
end
|
|
121
|
+
G -.->|No| H[Commit Blocked]
|
|
122
|
+
G -->|Yes| I[Commit Successful]
|
|
123
|
+
|
|
124
|
+
F --> CommitMessageHook
|
|
125
|
+
|
|
126
|
+
H -.->|Rework & Restage<br/>| K
|
|
127
|
+
|
|
128
|
+
K(["<kbd>git commit --amend</kbd>"])
|
|
129
|
+
|
|
130
|
+
K -.-> PrecommitHooks
|
|
131
|
+
|
|
132
|
+
L(["<kbd>git pull --rebase origin master</kbd>"])
|
|
133
|
+
|
|
134
|
+
L -.->|Rebase & Resolve Conflicts| M
|
|
135
|
+
|
|
136
|
+
M([<kbd>git add <...></kbd>])
|
|
137
|
+
|
|
138
|
+
M -.-> A
|
|
139
|
+
|
|
140
|
+
I -.~.-> J([<kbd>git push</kbd>])
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### GitHub Actions Workflow
|
|
145
|
+
|
|
146
|
+
Once a commit is pushed to the remote `master` branch, the GitHub Actions workflow `.github/workflows/release.yml` is triggered.
|
|
147
|
+
Note that the GitHub Actions workflow might push commits to the remote repository.
|
|
148
|
+
This means your local branch will be behind the remote branch.
|
|
149
|
+
|
|
150
|
+
In order for this workflow to run properly, two secrets need to be configured (`Settings > Secrets and variables > Actions`):
|
|
151
|
+
|
|
152
|
+
- `GH_PAT`: Personal Access Token with enhanced permissions (see PAT Setup below)
|
|
153
|
+
- `PYPI_API_TOKEN`: Generate from PyPI account settings
|
|
154
|
+
|
|
155
|
+
The default `GITHUB_TOKEN` has limited permissions and cannot trigger subsequent workflow runs or push to protected branches.
|
|
156
|
+
The PAT provides the necessary permissions for the automated release process.
|
|
157
|
+
The PAT is created as follows:
|
|
158
|
+
|
|
159
|
+
1. Go to GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)
|
|
160
|
+
2. Click "Generate new token (classic)"
|
|
161
|
+
3. Set expiration and select these scopes:
|
|
162
|
+
- `repo` (Full control of private repositories)
|
|
163
|
+
- `workflow` (Update GitHub Action workflows)
|
|
164
|
+
4. Copy the token and add it as `GH_PAT` secret in repository settings
|
|
165
|
+
|
|
166
|
+
Note that GitHub Actions bot must have write permissions to the repository.
|
|
167
|
+
|
|
168
|
+
The following diagram illustrates this GitHub Actions workflow:
|
|
169
|
+
|
|
170
|
+
```mermaid
|
|
171
|
+
---
|
|
172
|
+
config:
|
|
173
|
+
themeVariables:
|
|
174
|
+
fontSize: "11px"
|
|
175
|
+
---
|
|
176
|
+
graph TD
|
|
177
|
+
|
|
178
|
+
A0(<kdb>git commit</kbd>)
|
|
179
|
+
|
|
180
|
+
A1(<kdb>git commit --amend</kdb>)
|
|
181
|
+
|
|
182
|
+
A(<kbd>git push origin master</kbd>) -->|Trigger GitHub Actions Workflow on <kbd>push</kbd>| GitHubActions
|
|
183
|
+
|
|
184
|
+
A2(<kbd>git push origin master --force</kbd>) -->|Trigger GitHub Actions Workflow on <kbd>push</kbd>| GitHubActions
|
|
185
|
+
|
|
186
|
+
A0 -.->|Trigger Pre-commit Workflow & Commit| A
|
|
187
|
+
A1 -.->|Trigger Pre-commit Workflow & Commit| A2
|
|
188
|
+
|
|
189
|
+
subgraph GitHubActions ["GitHub Actions Environment Setup"]
|
|
190
|
+
B["<b>Checkout Repository</b><br/>Retrieve the full repository history on the latest Ubuntu runner"]
|
|
191
|
+
C["<b>Setup Python Environment</b><br/>Configure the required Python version and install Poetry"]
|
|
192
|
+
D["<b>Install Dependencies</b><br/>Install all project dependencies, including development ones"]
|
|
193
|
+
B --> C --> D
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
GitHubActions -.->|Failure<br/>Rework & Restage| A3
|
|
197
|
+
A3(<kdb>git commit --amend</kdb>)
|
|
198
|
+
|
|
199
|
+
GitHubActions -->|Environment Setup Complete| QualityChecks
|
|
200
|
+
|
|
201
|
+
subgraph QualityChecks ["CI Quality Validation"]
|
|
202
|
+
F["<b>Ruff Linting</b><br/>Validate code style and enforce formatting rules"]
|
|
203
|
+
G["<b>MyPy Type Checking</b><br/>Static type analysis"]
|
|
204
|
+
H["<b>Test Suite</b><br/>Run all automated tests"]
|
|
205
|
+
F --> G --> H
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
QualityChecks -.->|Failure<br/>Rework & Restage| A3
|
|
209
|
+
|
|
210
|
+
QualityChecks -->|CI Quality Checks Passed| GitConfig
|
|
211
|
+
|
|
212
|
+
subgraph GitConfig ["Git Configuration"]
|
|
213
|
+
J["<b>Configure Git Identity</b><br/>Set the automated commit author for CI operations"]
|
|
214
|
+
K["<b>Setup Authentication</b><br/>Enable secure access to the repository with release permissions (requires <kbd>GH_PAT</kbd>)"]
|
|
215
|
+
J --> K
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
GitConfig -->|Git Configured| VersionAnalysis
|
|
219
|
+
|
|
220
|
+
subgraph VersionAnalysis ["Semantic Version Analysis"]
|
|
221
|
+
N["<b>Execute bump_version.py</b><br/>Analyze commits since last tag to decide on version bump and bump level"]
|
|
222
|
+
P{Version Bump Required?}
|
|
223
|
+
N --> P
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
VersionAnalysis -->|No Version Change<br/>Skip Release Process| DocDeployment
|
|
227
|
+
VersionAnalysis -->|Version Bump Required| ReleaseProcess
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
subgraph ReleaseProcess ["Release & Publishing"]
|
|
231
|
+
R["<b>Update Version & Changelog</b><br/>Write new version and regenerate release notes."]
|
|
232
|
+
S["<b>Commit & Push</b><br/>Commit updated files and push to the default branch."]
|
|
233
|
+
T["<b>Publish to PyPI</b><br/>Build and upload distributions in one step."]
|
|
234
|
+
U["<b>Create GitHub Release</b><br/>Publish tag and attach changelog."]
|
|
235
|
+
R --> S --> T --> U
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
ReleaseProcess -->|Release Complete| DocDeployment
|
|
240
|
+
|
|
241
|
+
subgraph DocDeployment ["Documentation Deployment"]
|
|
242
|
+
X["<b>Generate API Documentation</b><br/>Automatically build API docs and update navigation"]
|
|
243
|
+
Y["<b>Install Package for Docs</b><br/>Prepare project for import-based documentation"]
|
|
244
|
+
Z["<b>Deploy to GitHub Pages</b><br/>Publish updated documentation site"]
|
|
245
|
+
X --> Y --> Z
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
@@ -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,24 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: onesecondtrader
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place.
|
|
5
|
-
Author: Nils P. Kujath
|
|
6
|
-
Author-email: 63961429+NilsKujath@users.noreply.github.com
|
|
7
|
-
Requires-Python: >=3.11
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
-
Requires-Dist: pandas (>=2.3.1,<3.0.0)
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
|
-
# OneSecondTrader
|
|
16
|
-
|
|
17
|
-
[](https://github.com/nilskujath/onesecondtrader/actions/workflows/release.yml)
|
|
18
|
-
[](https://www.onesecondtrader.com)
|
|
19
|
-
[](https://pypi.org/project/onesecondtrader/)
|
|
20
|
-
[](https://github.com/nilskujath/onesecondtrader/blob/master/LICENSE)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
For documentation, please visit [onesecondtrader.com](https://www.onesecondtrader.com).
|
|
24
|
-
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
onesecondtrader/__init__.py,sha256=EKXVvlpWyePQfeBc8V61sDpBfyh5aONvkBmgonzc930,250
|
|
2
|
-
onesecondtrader/monitoring.py,sha256=TGm53SD_TA4Xa57Go91BGHV3SEIA6MflwSO63r9Jt6g,366
|
|
3
|
-
onesecondtrader-0.6.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
4
|
-
onesecondtrader-0.6.0.dist-info/METADATA,sha256=RN3j_ySqSFRTgvxqdsiGG-Z3oGERZaMg1DoPFE5Xy40,1220
|
|
5
|
-
onesecondtrader-0.6.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
6
|
-
onesecondtrader-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|