onesecondtrader 0.17.0__tar.gz → 0.19.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onesecondtrader
3
- Version: 0.17.0
3
+ Version: 0.19.0
4
4
  Summary: The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place.
5
5
  License-File: LICENSE
6
6
  Author: Nils P. Kujath
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "onesecondtrader"
3
- version = "0.17.0"
3
+ version = "0.19.0"
4
4
  description = "The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place."
5
5
  authors = [
6
6
  {name = "Nils P. Kujath",email = "63961429+NilsKujath@users.noreply.github.com"}
@@ -0,0 +1,238 @@
1
+ """
2
+ Core module containing the backbone of OneSecondTrader's event-driven architecture.
3
+ """
4
+
5
+ import abc
6
+ import dataclasses
7
+ import enum
8
+ import pandas as pd
9
+ import queue
10
+ import threading
11
+ import uuid
12
+
13
+ from collections import defaultdict
14
+
15
+
16
+ class Models:
17
+ """
18
+ Namespace for all models.
19
+ """
20
+
21
+ class RecordType(enum.Enum):
22
+ OHLCV_1S = 32
23
+ OHLCV_1M = 33
24
+ OHLCV_1H = 34
25
+ OHLCV_1D = 35
26
+
27
+ class OrderSide(enum.Enum):
28
+ BUY = enum.auto()
29
+ SELL = enum.auto()
30
+
31
+ class OrderType(enum.Enum):
32
+ MARKET = enum.auto()
33
+ LIMIT = enum.auto()
34
+ STOP = enum.auto()
35
+ STOP_LIMIT = enum.auto()
36
+
37
+ class OrderRejectionReason(enum.Enum):
38
+ INSUFFICIENT_FUNDS = enum.auto()
39
+ MARKET_CLOSED = enum.auto()
40
+ UNKNOWN = enum.auto()
41
+
42
+ class CancelRejectionReason(enum.Enum):
43
+ ORDER_ALREADY_FILLED = enum.auto()
44
+ ORDER_ALREADY_CANCELLED = enum.auto()
45
+ ORDER_PENDING_EXECUTION = enum.auto()
46
+ MARKET_CLOSED = enum.auto()
47
+ UNKNOWN = enum.auto()
48
+
49
+ class ModifyRejectionReason(enum.Enum):
50
+ ORDER_ALREADY_FILLED = enum.auto()
51
+ ORDER_ALREADY_CANCELLED = enum.auto()
52
+ ORDER_PENDING_EXECUTION = enum.auto()
53
+ ORDER_NOT_FOUND = enum.auto()
54
+ INVALID_PRICE = enum.auto()
55
+ INVALID_QUANTITY = enum.auto()
56
+ MARKET_CLOSED = enum.auto()
57
+ UNKNOWN = enum.auto()
58
+
59
+ class TimeInForce(enum.Enum):
60
+ GTC = enum.auto()
61
+ DAY = enum.auto()
62
+ IOC = enum.auto()
63
+ FOK = enum.auto()
64
+
65
+
66
+ class Events:
67
+ """
68
+ Namespace for all events.
69
+ """
70
+
71
+ # BASE EVENT
72
+ @dataclasses.dataclass(kw_only=True, frozen=True)
73
+ class BaseEvent:
74
+ ts_event: pd.Timestamp = dataclasses.field(
75
+ default_factory=lambda: pd.Timestamp.now(tz="UTC")
76
+ )
77
+
78
+ # SYSTEM EVENTS
79
+ @dataclasses.dataclass(kw_only=True, frozen=True)
80
+ class SystemEvent(BaseEvent):
81
+ pass
82
+
83
+ @dataclasses.dataclass(kw_only=True, frozen=True)
84
+ class SystemShutdown(SystemEvent):
85
+ pass
86
+
87
+ # MARKET EVENTS
88
+ @dataclasses.dataclass(kw_only=True, frozen=True)
89
+ class MarketEvent(BaseEvent):
90
+ pass
91
+
92
+ @dataclasses.dataclass(kw_only=True, frozen=True)
93
+ class IncomingBar(MarketEvent):
94
+ ts_event: pd.Timestamp
95
+ symbol: str
96
+ record_type: Models.RecordType
97
+ open: float
98
+ high: float
99
+ low: float
100
+ close: float
101
+ volume: int | None = None
102
+
103
+ # BROKER REQUESTS EVENTS
104
+ @dataclasses.dataclass(kw_only=True, frozen=True)
105
+ class BrokerRequestEvent(BaseEvent):
106
+ pass
107
+
108
+ @dataclasses.dataclass(kw_only=True, frozen=True)
109
+ class SubmitOrder(BrokerRequestEvent):
110
+ order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
111
+ symbol: str
112
+ order_type: Models.OrderType
113
+ side: Models.OrderSide
114
+ quantity: float
115
+ limit_price: float | None = None
116
+ stop_price: float | None = None
117
+ time_in_force: Models.TimeInForce = Models.TimeInForce.GTC
118
+
119
+ @dataclasses.dataclass(kw_only=True, frozen=True)
120
+ class ModifyOrder(BrokerRequestEvent):
121
+ order_id: uuid.UUID
122
+ quantity: float | None = None
123
+ limit_price: float | None = None
124
+ stop_price: float | None = None
125
+
126
+ @dataclasses.dataclass(kw_only=True, frozen=True)
127
+ class CancelOrder(BrokerRequestEvent):
128
+ order_id: uuid.UUID
129
+
130
+ # BROKER RESPONSE EVENTS
131
+ @dataclasses.dataclass(kw_only=True, frozen=True)
132
+ class BrokerResponseEvent(BaseEvent):
133
+ ts_broker: pd.Timestamp
134
+
135
+ @dataclasses.dataclass(kw_only=True, frozen=True)
136
+ class OrderSubmitted(BrokerResponseEvent):
137
+ order_id: uuid.UUID
138
+ broker_order_id: str | None = None
139
+
140
+ @dataclasses.dataclass(kw_only=True, frozen=True)
141
+ class OrderModified(BrokerResponseEvent):
142
+ order_id: uuid.UUID
143
+ broker_order_id: str | None = None
144
+
145
+ @dataclasses.dataclass(kw_only=True, frozen=True)
146
+ class Fill(BrokerResponseEvent):
147
+ fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
148
+ broker_fill_id: str | None = None
149
+ associated_order_id: uuid.UUID
150
+ symbol: str
151
+ side: Models.OrderSide
152
+ quantity_filled: float
153
+ fill_price: float
154
+ commission: float
155
+ exchange: str = "SIMULATED"
156
+
157
+ @dataclasses.dataclass(kw_only=True, frozen=True)
158
+ class OrderRejected(BrokerResponseEvent):
159
+ order_id: uuid.UUID
160
+ reason: Models.OrderRejectionReason
161
+
162
+ @dataclasses.dataclass(kw_only=True, frozen=True)
163
+ class OrderCancelled(BrokerResponseEvent):
164
+ order_id: uuid.UUID
165
+
166
+ @dataclasses.dataclass(kw_only=True, frozen=True)
167
+ class OrderExpired(BrokerResponseEvent):
168
+ order_id: uuid.UUID
169
+
170
+ @dataclasses.dataclass(kw_only=True, frozen=True)
171
+ class CancelRejected(BrokerResponseEvent):
172
+ order_id: uuid.UUID
173
+ reason: Models.CancelRejectionReason
174
+
175
+ @dataclasses.dataclass(kw_only=True, frozen=True)
176
+ class ModifyRejected(BrokerResponseEvent):
177
+ order_id: uuid.UUID
178
+ reason: Models.ModifyRejectionReason
179
+
180
+
181
+ class BaseConsumer(abc.ABC):
182
+ """
183
+ Base class for all consumers.
184
+ """
185
+
186
+ def __init__(self) -> None:
187
+ self._queue: queue.Queue[Events.BaseEvent] = queue.Queue()
188
+ self._thread = threading.Thread(target=self._consume, daemon=True)
189
+ self._thread.start()
190
+
191
+ @abc.abstractmethod
192
+ def on_event(self, event: Events.BaseEvent) -> None:
193
+ pass
194
+
195
+ def receive(self, event: Events.BaseEvent) -> None:
196
+ self._queue.put(event)
197
+
198
+ def _consume(self) -> None:
199
+ while True:
200
+ event = self._queue.get()
201
+ if isinstance(event, Events.SystemShutdown):
202
+ break
203
+ self.on_event(event)
204
+
205
+
206
+ class EventBus:
207
+ """
208
+ Event bus for publishing events to the consumers subscribed to them.
209
+ """
210
+
211
+ def __init__(self) -> None:
212
+ self._subscriptions: defaultdict[type[Events.BaseEvent], list[BaseConsumer]] = (
213
+ defaultdict(list)
214
+ )
215
+ self._lock: threading.Lock = threading.Lock()
216
+
217
+ def subscribe(self, subscriber: BaseConsumer, event_type: type[Events.BaseEvent]):
218
+ with self._lock:
219
+ if subscriber not in self._subscriptions[event_type]:
220
+ self._subscriptions[event_type].append(subscriber)
221
+
222
+ def unsubscribe(self, subscriber: BaseConsumer):
223
+ with self._lock:
224
+ for consumer_list in self._subscriptions.values():
225
+ if subscriber in consumer_list:
226
+ consumer_list.remove(subscriber)
227
+
228
+ def publish(self, event: Events.BaseEvent) -> None:
229
+ with self._lock:
230
+ consumers = list(self._subscriptions[type(event)])
231
+ for consumer in consumers:
232
+ consumer.receive(event)
233
+
234
+
235
+ event_bus = EventBus()
236
+ """
237
+ Global event bus instance.
238
+ """
@@ -1,105 +0,0 @@
1
- """
2
- Core module containing the backbone of OneSecondTrader's event-driven architecture.
3
- """
4
-
5
- import abc
6
- import dataclasses
7
- import enum
8
- import pandas as pd
9
- import queue
10
- import threading
11
-
12
- from collections import defaultdict
13
-
14
-
15
- class Models:
16
- """
17
- Namespace for all models.
18
- """
19
-
20
- class RecordType(enum.Enum):
21
- OHLCV_1S = 32
22
- OHLCV_1M = 33
23
- OHLCV_1H = 34
24
- OHLCV_1D = 35
25
-
26
-
27
- class Events:
28
- """
29
- Namespace for all events.
30
- """
31
-
32
- @dataclasses.dataclass(kw_only=True, frozen=True)
33
- class BaseEvent:
34
- ts_event: pd.Timestamp = dataclasses.field(
35
- default_factory=lambda: pd.Timestamp.now(tz="UTC")
36
- )
37
-
38
- @dataclasses.dataclass(kw_only=True, frozen=True)
39
- class SystemShutdown(BaseEvent):
40
- pass
41
-
42
- @dataclasses.dataclass(kw_only=True, frozen=True)
43
- class IncomingBar(BaseEvent):
44
- ts_event: pd.Timestamp
45
- symbol: str
46
- record_type: Models.RecordType
47
- open: float
48
- high: float
49
- low: float
50
- close: float
51
- volume: int | None = None
52
-
53
-
54
- class BaseConsumer(abc.ABC):
55
- """
56
- Base class for all consumers.
57
- """
58
-
59
- def __init__(self) -> None:
60
- self._queue: queue.Queue[Events.BaseEvent] = queue.Queue()
61
- self._thread = threading.Thread(target=self._consume, daemon=True)
62
- self._thread.start()
63
-
64
- @abc.abstractmethod
65
- def on_event(self, event: Events.BaseEvent) -> None:
66
- pass
67
-
68
- def receive(self, event: Events.BaseEvent) -> None:
69
- self._queue.put(event)
70
-
71
- def _consume(self) -> None:
72
- while True:
73
- event = self._queue.get()
74
- if isinstance(event, Events.SystemShutdown):
75
- break
76
- self.on_event(event)
77
-
78
-
79
- class EventBus:
80
- """
81
- Event bus for publishing events to the consumers subscribed to them.
82
- """
83
-
84
- def __init__(self) -> None:
85
- self._subscriptions: defaultdict[type[Events.BaseEvent], list[BaseConsumer]] = (
86
- defaultdict(list)
87
- )
88
- self._lock: threading.Lock = threading.Lock()
89
-
90
- def subscribe(self, subscriber: BaseConsumer, event_type: type[Events.BaseEvent]):
91
- with self._lock:
92
- if subscriber not in self._subscriptions[event_type]:
93
- self._subscriptions[event_type].append(subscriber)
94
-
95
- def unsubscribe(self, subscriber: BaseConsumer):
96
- with self._lock:
97
- for consumer_list in self._subscriptions.values():
98
- if subscriber in consumer_list:
99
- consumer_list.remove(subscriber)
100
-
101
- def publish(self, event: Events.BaseEvent) -> None:
102
- with self._lock:
103
- consumers = list(self._subscriptions[type(event)])
104
- for consumer in consumers:
105
- consumer.receive(event)