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.
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/PKG-INFO +1 -1
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/pyproject.toml +1 -1
- onesecondtrader-0.19.0/src/onesecondtrader/core.py +238 -0
- onesecondtrader-0.17.0/src/onesecondtrader/core.py +0 -105
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/LICENSE +0 -0
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/README.md +0 -0
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/src/onesecondtrader/__init__.py +0 -0
- {onesecondtrader-0.17.0 → onesecondtrader-0.19.0}/src/onesecondtrader/indicators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 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.
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|