onesecondtrader 0.14.2__py3-none-any.whl → 0.15.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/core/models.py +16 -0
- onesecondtrader/core/portfolio.py +121 -303
- onesecondtrader/messaging/events.py +32 -6
- {onesecondtrader-0.14.2.dist-info → onesecondtrader-0.15.0.dist-info}/METADATA +1 -1
- {onesecondtrader-0.14.2.dist-info → onesecondtrader-0.15.0.dist-info}/RECORD +7 -7
- {onesecondtrader-0.14.2.dist-info → onesecondtrader-0.15.0.dist-info}/WHEEL +0 -0
- {onesecondtrader-0.14.2.dist-info → onesecondtrader-0.15.0.dist-info}/licenses/LICENSE +0 -0
onesecondtrader/core/models.py
CHANGED
|
@@ -186,3 +186,19 @@ class StrategyShutdownMode(enum.Enum):
|
|
|
186
186
|
|
|
187
187
|
SOFT = enum.auto()
|
|
188
188
|
HARD = enum.auto()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class SymbolShutdownMode(enum.Enum):
|
|
192
|
+
"""
|
|
193
|
+
Enum for symbol shutdown modes.
|
|
194
|
+
|
|
195
|
+
**Attributes:**
|
|
196
|
+
|
|
197
|
+
| Enum | Value | Description |
|
|
198
|
+
|------|-------|-------------|
|
|
199
|
+
| `SOFT` | `enum.auto()` | Do not open new positions; wait until current positions close naturally |
|
|
200
|
+
| `HARD` | `enum.auto()` | Close all positions immediately with market orders |
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
SOFT = enum.auto()
|
|
204
|
+
HARD = enum.auto()
|
|
@@ -1,359 +1,177 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides the Portfolio class.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
1
|
import threading
|
|
6
|
-
|
|
2
|
+
|
|
7
3
|
from onesecondtrader import messaging
|
|
8
4
|
from onesecondtrader.messaging import events
|
|
9
|
-
from onesecondtrader.monitoring import console
|
|
10
|
-
from onesecondtrader.brokers import base_broker
|
|
11
5
|
from onesecondtrader.strategies import base_strategy
|
|
12
|
-
from onesecondtrader.
|
|
6
|
+
from onesecondtrader.monitoring import console
|
|
7
|
+
from onesecondtrader.core import models
|
|
13
8
|
|
|
14
9
|
|
|
15
10
|
class Portfolio:
|
|
16
|
-
|
|
17
|
-
The Portfolio class orchestrates the trading infrastructure's components.
|
|
18
|
-
It manages the broker connection, market data reception, and strategy execution.
|
|
19
|
-
|
|
20
|
-
Multiple instances of the same Strategy class can be registered concurrently.
|
|
21
|
-
Symbol ownership is exclusive and enforced by the portfolio: each symbol may be
|
|
22
|
-
owned by at most one strategy instance at a time. Use `add_strategy()` with a list
|
|
23
|
-
of symbols or `assign_symbols(...)` with a specific strategy and a list of symbols
|
|
24
|
-
to claim symbols; `owner_of(symbol)` returns the current owner.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(
|
|
28
|
-
self,
|
|
29
|
-
event_bus: messaging.EventBus | None = None,
|
|
30
|
-
broker_class: type[base_broker.BaseBroker] | None = None,
|
|
31
|
-
):
|
|
11
|
+
def __init__(self, event_bus: messaging.EventBus | None = None):
|
|
32
12
|
"""
|
|
33
|
-
Initialize the Portfolio class
|
|
13
|
+
Initialize the Portfolio class and subscribe to events.
|
|
14
|
+
Most importantly, the `symbol_to_strategy` registry is initialized,
|
|
15
|
+
which keeps track of which symbols are currently assigned to which strategy
|
|
16
|
+
in order to enforce exclusive symbol ownership.
|
|
34
17
|
|
|
35
18
|
Args:
|
|
36
|
-
event_bus (EventBus | None): Event bus to use; defaults to
|
|
37
|
-
|
|
38
|
-
broker_class (type[base_broker.BaseBroker] | None): Broker class to
|
|
39
|
-
instantiate and connect. Must be a subclass of BaseBroker.
|
|
19
|
+
event_bus (messaging.EventBus | None): Event bus to use; defaults to
|
|
20
|
+
messaging.system_event_bus when None.
|
|
40
21
|
|
|
41
22
|
Attributes:
|
|
42
|
-
self.event_bus (eventbus.EventBus): Event bus used for communication between
|
|
43
|
-
the trading infrastructure's components.
|
|
44
23
|
self._lock (threading.Lock): Lock for thread-safe operations.
|
|
45
|
-
self.
|
|
46
|
-
|
|
47
|
-
self.
|
|
48
|
-
|
|
49
|
-
a time.
|
|
50
|
-
self.strategy_removal_pending (set[base_strategy.Strategy]): Set of
|
|
51
|
-
strategies that are still active but marked for removal once all symbols
|
|
52
|
-
are released.
|
|
53
|
-
self.broker (base_broker.BaseBroker | None): Instantiated broker; may be
|
|
54
|
-
disconnected if connect failed.
|
|
24
|
+
self.event_bus (messaging.EventBus): Event bus used for communication
|
|
25
|
+
between the trading infrastructure's components.
|
|
26
|
+
self.symbols_to_strategy (dict[str, base_strategy.Strategy]): Registry of
|
|
27
|
+
symbols to strategies.
|
|
55
28
|
"""
|
|
56
|
-
# INITIALIZE EVENT BUS
|
|
57
29
|
# ------------------------------------------------------------------------------
|
|
30
|
+
# INITIALIZE LOCK FOR THREAD-SAFE OPERATIONS
|
|
31
|
+
self._lock: threading.Lock = threading.Lock()
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------------------------
|
|
34
|
+
# INITIALIZE EVENT BUS AND SUBSCRIBE TO EVENTS
|
|
58
35
|
self.event_bus: messaging.EventBus = (
|
|
59
36
|
event_bus if event_bus else messaging.system_event_bus
|
|
60
37
|
)
|
|
61
|
-
|
|
62
|
-
# SUBSCRIBE HANDLER METHODS TO EVENTS VIA event_bus.subscribe
|
|
63
|
-
# ------------------------------------------------------------------------------
|
|
64
38
|
self.event_bus.subscribe(events.Strategy.SymbolRelease, self.on_symbol_release)
|
|
65
39
|
|
|
66
|
-
# INITIALIZE LOCK FOR THREAD-SAFE OPERATIONS WITHIN THE PORTFOLIO
|
|
67
40
|
# ------------------------------------------------------------------------------
|
|
68
|
-
|
|
41
|
+
# INITIALIZE SYMBOLS TO STRATEGY REGISTRY
|
|
42
|
+
self.symbols_to_strategy: dict[str, base_strategy.Strategy] = {}
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self._symbol_owner: dict[str, base_strategy.Strategy] = {}
|
|
44
|
+
def on_symbol_release(self, event: messaging.events.Base.Event) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Event handler for symbol release events (`events.Strategy.SymbolRelease`).
|
|
47
|
+
The symbol is removed from the `symbols_to_strategy` registry.
|
|
75
48
|
|
|
76
|
-
|
|
49
|
+
Args:
|
|
50
|
+
event (messaging.events.Base.Event): Symbol release event.
|
|
51
|
+
"""
|
|
77
52
|
# ------------------------------------------------------------------------------
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
if broker_class is None or not issubclass(broker_class, base_broker.BaseBroker):
|
|
81
|
-
broker_name = (
|
|
82
|
-
getattr(broker_class, "__name__", str(broker_class))
|
|
83
|
-
if broker_class
|
|
84
|
-
else None
|
|
85
|
-
)
|
|
86
|
-
console.logger.error(
|
|
87
|
-
"Portfolio requires a valid broker_class (subclass of BaseBroker), "
|
|
88
|
-
f"got {broker_name}"
|
|
89
|
-
)
|
|
90
|
-
return
|
|
91
|
-
try:
|
|
92
|
-
self.broker = broker_class(self.event_bus)
|
|
93
|
-
except Exception as e:
|
|
94
|
-
console.logger.error(
|
|
95
|
-
f"Failed to instantiate broker "
|
|
96
|
-
f"{getattr(broker_class, '__name__', str(broker_class))}: {e}"
|
|
97
|
-
)
|
|
53
|
+
# IGNORE UNRELATED EVENT TYPES
|
|
54
|
+
if not isinstance(event, events.Strategy.SymbolRelease):
|
|
98
55
|
return
|
|
99
56
|
|
|
100
|
-
# CONNECT TO BROKER
|
|
101
57
|
# ------------------------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
58
|
+
# RELEASE SYMBOL FROM STRATEGY
|
|
59
|
+
symbol = event.symbol
|
|
60
|
+
with self._lock:
|
|
61
|
+
if symbol in self.symbols_to_strategy:
|
|
62
|
+
del self.symbols_to_strategy[symbol]
|
|
63
|
+
console.logger.info(
|
|
64
|
+
f"on_symbol_release: symbol {symbol} released from "
|
|
65
|
+
f"{getattr(event.strategy, 'name', type(event.strategy).__name__)}"
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
console.logger.warning(
|
|
69
|
+
f"on_symbol_release: symbol {symbol} not owned by "
|
|
70
|
+
f"{getattr(event.strategy, 'name', type(event.strategy).__name__)}"
|
|
107
71
|
)
|
|
108
|
-
except Exception as e:
|
|
109
|
-
console.logger.error(f"Broker connect failed: {e}")
|
|
110
72
|
|
|
111
|
-
def
|
|
112
|
-
self,
|
|
113
|
-
strategy_instance: base_strategy.Strategy,
|
|
114
|
-
symbols: Iterable[str] | None = None,
|
|
73
|
+
def assign_symbols(
|
|
74
|
+
self, strategy_instance: base_strategy.Strategy, symbols: list[str]
|
|
115
75
|
) -> bool:
|
|
116
76
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
If symbols are provided, potential conflicts are checked first under a lock.
|
|
120
|
-
If any conflicts exist, no symbols are assigned; a warning is logged listing
|
|
121
|
-
both non_conflicting and conflicting symbols and instructions to use
|
|
122
|
-
assign_symbols(...) are provided.
|
|
123
|
-
If no conflicts exist, all provided symbols are claimed by the strategy.
|
|
77
|
+
Assign a list of symbols to a strategy if no conflicts exist and notify the
|
|
78
|
+
strategy of the assignment.
|
|
124
79
|
|
|
125
80
|
Args:
|
|
126
|
-
strategy_instance (base_strategy.Strategy): Strategy instance to
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
bool: True if the strategy was registered, False otherwise.
|
|
81
|
+
strategy_instance (base_strategy.Strategy): Strategy instance to assign
|
|
82
|
+
symbols to.
|
|
83
|
+
symbols (list[str]): List of symbols to assign.
|
|
132
84
|
"""
|
|
133
|
-
# VALIDATE THAT INSTANCE IS A SUBCLASS OF base_strategy.Strategy
|
|
134
85
|
# ------------------------------------------------------------------------------
|
|
86
|
+
# VALIDATE THAT INSTANCE IS A SUBCLASS OF base_strategy.Strategy
|
|
135
87
|
if not isinstance(strategy_instance, base_strategy.Strategy):
|
|
136
|
-
console.logger.error("
|
|
88
|
+
console.logger.error("assign_symbols: strategy must inherit from Strategy")
|
|
137
89
|
return False
|
|
138
90
|
|
|
139
|
-
# ADD STRATEGY INSTANCE TO REGISTRY IF NOT ALREADY REGISTERED
|
|
140
91
|
# ------------------------------------------------------------------------------
|
|
92
|
+
# CHECK FOR CONFLICTS
|
|
93
|
+
non_conflicting: list[str] = []
|
|
94
|
+
conflicting: list[str] = []
|
|
141
95
|
with self._lock:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# ASSIGN SYMBOLS IF PROVIDED AND NO CONFLICTS EXIST, ELSE LOG WARNING
|
|
148
|
-
# ------------------------------------------------------------------------------
|
|
149
|
-
if symbols is not None:
|
|
150
|
-
# Create an ordered list of unique, non-empty, trimmed symbols
|
|
151
|
-
symbols_list = list(
|
|
152
|
-
dict.fromkeys(s.strip() for s in symbols if s and s.strip())
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# Check for conflicts, claim symbols for strategy if no conflicts arise
|
|
156
|
-
if symbols_list:
|
|
157
|
-
non_conflicting: list[str] = []
|
|
158
|
-
conflicting: list[str] = []
|
|
159
|
-
with self._lock:
|
|
160
|
-
for sym in symbols_list:
|
|
161
|
-
owner = self._symbol_owner.get(sym)
|
|
162
|
-
if owner is None or owner is strategy_instance:
|
|
163
|
-
non_conflicting.append(sym)
|
|
164
|
-
else:
|
|
165
|
-
conflicting.append(sym)
|
|
166
|
-
if conflicting:
|
|
167
|
-
console.logger.warning(
|
|
168
|
-
"add_strategy: symbols not assigned due to conflicts; "
|
|
169
|
-
"use Portfolio.assign_symbols(...) after resolving. "
|
|
170
|
-
f"non_conflicting={non_conflicting}, conflicts={conflicting}"
|
|
171
|
-
)
|
|
172
|
-
return False
|
|
96
|
+
for symbol in symbols:
|
|
97
|
+
owner = self.symbols_to_strategy.get(symbol)
|
|
98
|
+
if owner is None:
|
|
99
|
+
non_conflicting.append(symbol)
|
|
173
100
|
else:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
101
|
+
conflicting.append(symbol)
|
|
102
|
+
if conflicting:
|
|
103
|
+
console.logger.warning(
|
|
104
|
+
"assign_symbols: symbols not assigned due to conflicts; "
|
|
105
|
+
"use Portfolio.assign_symbols(...) after resolving. "
|
|
106
|
+
f"non_conflicting={non_conflicting}, conflicts={conflicting}"
|
|
107
|
+
)
|
|
108
|
+
return False
|
|
109
|
+
else:
|
|
110
|
+
# --------------------------------------------------------------------------
|
|
111
|
+
# ASSIGN SYMBOLS TO REGISTRY
|
|
112
|
+
for symbol in symbols:
|
|
113
|
+
self.symbols_to_strategy[symbol] = strategy_instance
|
|
114
|
+
|
|
115
|
+
# --------------------------------------------------------------------------
|
|
116
|
+
# PUBLISH SYMBOL ASSIGNMENT EVENT
|
|
117
|
+
# noinspection PyArgumentList
|
|
118
|
+
self.event_bus.publish(
|
|
119
|
+
events.Strategy.SymbolAssignment(
|
|
120
|
+
strategy=strategy_instance,
|
|
121
|
+
symbol_list=symbols,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return True
|
|
185
125
|
|
|
186
|
-
def
|
|
126
|
+
def unassign_symbols(
|
|
187
127
|
self,
|
|
188
|
-
|
|
189
|
-
shutdown_mode:
|
|
128
|
+
symbols: list[str],
|
|
129
|
+
shutdown_mode: models.SymbolShutdownMode = models.SymbolShutdownMode.SOFT,
|
|
190
130
|
) -> bool:
|
|
191
131
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
# IF STRATEGY IS REGISTERED, MARK IT FOR REMOVAL AND PUBLISH STOP TRADING EVENT
|
|
203
|
-
# ------------------------------------------------------------------------------
|
|
204
|
-
with self._lock:
|
|
205
|
-
if strategy_instance not in self._strategies:
|
|
206
|
-
console.logger.warning("remove_strategy: strategy not registered")
|
|
207
|
-
return False
|
|
208
|
-
self.strategy_removal_pending.add(strategy_instance)
|
|
209
|
-
|
|
210
|
-
# PUBLISH STOP TRADING REQUEST TO EVENT BUS
|
|
211
|
-
# ------------------------------------------------------------------------------
|
|
212
|
-
self.event_bus.publish(
|
|
213
|
-
events.Strategy.StopTrading(
|
|
214
|
-
strategy=strategy_instance,
|
|
215
|
-
shutdown_mode=shutdown_mode,
|
|
216
|
-
)
|
|
217
|
-
)
|
|
218
|
-
return True
|
|
219
|
-
|
|
220
|
-
def assign_symbols(
|
|
221
|
-
self, strategy_instance: base_strategy.Strategy, symbols: Iterable[str]
|
|
222
|
-
):
|
|
223
|
-
"""
|
|
224
|
-
Assign a list of symbols to a strategy.
|
|
132
|
+
Unassign a list of symbols from their owning strategy if all of them have
|
|
133
|
+
previously been assigned to a strategy.
|
|
134
|
+
Calling this methods will request the owning strategy to stop trading the symbol
|
|
135
|
+
in the manner dictated via the `shutdown_mode` argument (default to soft
|
|
136
|
+
shutdown, i.e. wait for open positions to close naturally and release symbols
|
|
137
|
+
once they are flat).
|
|
138
|
+
After the owning strategy has released the symbol, the symbol is unassigned from
|
|
139
|
+
the portfolio via the `on_symbol_release` event handler.
|
|
225
140
|
|
|
226
141
|
Args:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
142
|
+
symbols (list[str]): List of symbols to unassign.
|
|
143
|
+
shutdown_mode (models.SymbolShutdownMode): Shutdown mode to use. Defaults
|
|
144
|
+
to `models.SymbolShutdownMode.SOFT`.
|
|
230
145
|
"""
|
|
231
|
-
# IF STRATEGY IS REGISTERED, MARK IT FOR REMOVAL AND PUBLISH STOP TRADING EVENT
|
|
232
146
|
# ------------------------------------------------------------------------------
|
|
147
|
+
# CHECK THAT SYMBOLS ARE REGISTERED
|
|
148
|
+
conflicting: list[str] = []
|
|
233
149
|
with self._lock:
|
|
234
|
-
|
|
235
|
-
|
|
150
|
+
for symbol in symbols:
|
|
151
|
+
if symbol not in self.symbols_to_strategy:
|
|
152
|
+
conflicting.append(symbol)
|
|
153
|
+
if conflicting:
|
|
154
|
+
console.logger.warning(
|
|
155
|
+
"unassign_symbols: symbols not unassigned due to conflicts; "
|
|
156
|
+
f"conflicts={conflicting}. "
|
|
157
|
+
f"Use Portfolio.unassign_symbols(...) after resolving."
|
|
158
|
+
)
|
|
236
159
|
return False
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if symbols is not None:
|
|
244
|
-
# Create an ordered list of unique, non-empty, trimmed symbols
|
|
245
|
-
symbols_list = list(
|
|
246
|
-
dict.fromkeys(s.strip() for s in symbols if s and s.strip())
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
# Check for conflicts, claim symbols for strategy if no conflicts arise
|
|
250
|
-
if symbols_list:
|
|
251
|
-
non_conflicting: list[str] = []
|
|
252
|
-
conflicting: list[str] = []
|
|
253
|
-
with self._lock:
|
|
254
|
-
for sym in symbols_list:
|
|
255
|
-
owner = self._symbol_owner.get(sym)
|
|
256
|
-
if owner is None or owner is strategy_instance:
|
|
257
|
-
non_conflicting.append(sym)
|
|
258
|
-
else:
|
|
259
|
-
conflicting.append(sym)
|
|
260
|
-
if conflicting:
|
|
261
|
-
console.logger.warning(
|
|
262
|
-
"assign_symbols: symbols not assigned due to conflicts; "
|
|
263
|
-
"use Portfolio.assign_symbols(...) after resolving. "
|
|
264
|
-
f"non_conflicting={non_conflicting}, conflicts={conflicting}"
|
|
265
|
-
)
|
|
266
|
-
return False
|
|
267
|
-
else:
|
|
268
|
-
for sym in symbols_list:
|
|
269
|
-
self._symbol_owner[sym] = strategy_instance
|
|
160
|
+
else:
|
|
161
|
+
# ----------------------------------------------------------------------
|
|
162
|
+
# PUBLISH STOP TRADING SYMBOL EVENT FOR EACH SYMBOL
|
|
163
|
+
for symbol in symbols:
|
|
164
|
+
# noinspection PyArgumentList
|
|
270
165
|
self.event_bus.publish(
|
|
271
|
-
events.Strategy.
|
|
272
|
-
strategy=
|
|
273
|
-
|
|
166
|
+
events.Strategy.StopTradingSymbol(
|
|
167
|
+
strategy=self.symbols_to_strategy[symbol],
|
|
168
|
+
symbol=symbol,
|
|
169
|
+
shutdown_mode=shutdown_mode,
|
|
274
170
|
)
|
|
275
171
|
)
|
|
276
|
-
return True
|
|
277
|
-
|
|
278
|
-
def unassign_symbols(
|
|
279
|
-
self, strategy: base_strategy.Strategy, symbols: Iterable[str]
|
|
280
|
-
) -> list[str]:
|
|
281
|
-
if not isinstance(strategy, base_strategy.Strategy):
|
|
282
|
-
console.logger.error(
|
|
283
|
-
"unassign_symbols: strategy must inherit from Strategy"
|
|
284
|
-
)
|
|
285
|
-
return []
|
|
286
|
-
symbols_list = list(
|
|
287
|
-
dict.fromkeys(s.strip() for s in symbols if s and s.strip())
|
|
288
|
-
)
|
|
289
|
-
if not symbols_list:
|
|
290
|
-
return []
|
|
291
|
-
removed: list[str] = []
|
|
292
|
-
with self._lock:
|
|
293
|
-
for sym in symbols_list:
|
|
294
|
-
if self._symbol_owner.get(sym) is strategy:
|
|
295
|
-
del self._symbol_owner[sym]
|
|
296
|
-
removed.append(sym)
|
|
297
|
-
if removed:
|
|
298
|
-
strategy.remove_symbols(removed)
|
|
299
|
-
return removed
|
|
300
|
-
|
|
301
|
-
def owner_of(self, symbol: str) -> base_strategy.Strategy | None:
|
|
302
|
-
"""
|
|
303
|
-
Return the owning strategy for a symbol or None if unowned.
|
|
304
|
-
"""
|
|
305
|
-
with self._lock:
|
|
306
|
-
return self._symbol_owner.get(symbol)
|
|
307
|
-
|
|
308
|
-
def on_symbol_release(self, event: events.Base.Event) -> None:
|
|
309
|
-
"""
|
|
310
|
-
Handle symbol release events. Ignores unrelated event types.
|
|
311
|
-
|
|
312
|
-
If a symbol is released, it is unassigned from the strategy that owns it
|
|
313
|
-
(inside the symbol_owner registry). If the strategy is marked for removal and
|
|
314
|
-
has no more symbols assigned to it, it is automatically deregistered.
|
|
315
|
-
"""
|
|
316
|
-
|
|
317
|
-
# IGNORE UNRELATED EVENT TYPES
|
|
318
|
-
# ------------------------------------------------------------------------------
|
|
319
|
-
if not isinstance(event, events.Strategy.SymbolRelease):
|
|
320
|
-
return
|
|
321
|
-
|
|
322
|
-
# IF STRATEGY IS REGISTERED, RELEASE SYMBOL FROM STRATEGY
|
|
323
|
-
# ------------------------------------------------------------------------------
|
|
324
|
-
# TODO This needs reworking; this will be the same logic as unassig_symbols
|
|
325
|
-
strategy = event.strategy
|
|
326
|
-
with self._lock:
|
|
327
|
-
if strategy not in self._strategies:
|
|
328
|
-
console.logger.warning("on_symbol_release: strategy not registered")
|
|
329
|
-
return
|
|
330
|
-
removed = self.unassign_symbols(strategy, [event.symbol])
|
|
331
|
-
if not removed:
|
|
332
|
-
console.logger.warning(
|
|
333
|
-
f"on_symbol_release: symbol {event.symbol} not owned by "
|
|
334
|
-
f"{getattr(event.strategy, 'name', type(event.strategy).__name__)}"
|
|
335
|
-
)
|
|
336
|
-
return
|
|
337
|
-
pending = False
|
|
338
|
-
with self._lock:
|
|
339
|
-
pending = strategy in self.strategy_removal_pending
|
|
340
|
-
has_owned_left = any(
|
|
341
|
-
owner is strategy for owner in self._symbol_owner.values()
|
|
342
|
-
)
|
|
343
|
-
if pending and not has_owned_left:
|
|
344
|
-
try:
|
|
345
|
-
if bool(strategy.is_flat()):
|
|
346
|
-
with self._lock:
|
|
347
|
-
if strategy in self._strategies:
|
|
348
|
-
self._strategies.remove(strategy)
|
|
349
|
-
self.strategy_removal_pending.discard(strategy)
|
|
350
172
|
console.logger.info(
|
|
351
|
-
f"
|
|
352
|
-
f"
|
|
173
|
+
f"unassign_symbols: trading stop for {symbol} trading strategy "
|
|
174
|
+
f"{self.symbols_to_strategy[symbol]} requested with shutdown"
|
|
175
|
+
f"mode {shutdown_mode.name}"
|
|
353
176
|
)
|
|
354
|
-
|
|
355
|
-
pass
|
|
356
|
-
console.logger.info(
|
|
357
|
-
f"on_symbol_release: symbol {event.symbol} released from "
|
|
358
|
-
f"{getattr(event.strategy, 'name', type(event.strategy).__name__)}"
|
|
359
|
-
)
|
|
177
|
+
return True
|
|
@@ -88,14 +88,17 @@ Dataclass field validation logic is grouped under the `_Validate` namespace.
|
|
|
88
88
|
E1[events.Strategy.SymbolRelease]
|
|
89
89
|
E2[events.Strategy.SymbolAssignment]
|
|
90
90
|
E3[events.Strategy.StopTrading]
|
|
91
|
+
E4[events.Strategy.StopTradingSymbol]
|
|
91
92
|
|
|
92
93
|
R5 --> E1
|
|
93
94
|
R5 --> E2
|
|
94
95
|
R5 --> E3
|
|
96
|
+
R5 --> E4
|
|
95
97
|
|
|
96
98
|
style E1 fill:#6F42C1,fill-opacity:0.3
|
|
97
99
|
style E2 fill:#6F42C1,fill-opacity:0.3
|
|
98
100
|
style E3 fill:#6F42C1,fill-opacity:0.3
|
|
101
|
+
style E4 fill:#6F42C1,fill-opacity:0.3
|
|
99
102
|
|
|
100
103
|
subgraph Market ["Market Update Event Messages"]
|
|
101
104
|
R1
|
|
@@ -163,11 +166,13 @@ Dataclass field validation logic is grouped under the `_Validate` namespace.
|
|
|
163
166
|
E1
|
|
164
167
|
E2
|
|
165
168
|
E3
|
|
169
|
+
E4
|
|
166
170
|
|
|
167
171
|
subgraph StrategyNamespace ["events.Strategy Namespace"]
|
|
168
172
|
E1
|
|
169
173
|
E2
|
|
170
174
|
E3
|
|
175
|
+
E4
|
|
171
176
|
end
|
|
172
177
|
|
|
173
178
|
end
|
|
@@ -242,7 +247,6 @@ class Base:
|
|
|
242
247
|
|
|
243
248
|
def __post_init__(self) -> None:
|
|
244
249
|
super().__post_init__()
|
|
245
|
-
_Validate.symbol(self.symbol, "Market event")
|
|
246
250
|
|
|
247
251
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
248
252
|
class Request(Event):
|
|
@@ -301,7 +305,6 @@ class Base:
|
|
|
301
305
|
|
|
302
306
|
def __post_init__(self) -> None:
|
|
303
307
|
super().__post_init__()
|
|
304
|
-
_Validate.symbol(self.symbol, f"Order {self.order_id}")
|
|
305
308
|
|
|
306
309
|
_Validate.timezone_aware(
|
|
307
310
|
self.order_expiration, "order_expiration", f"Order {self.order_id}"
|
|
@@ -583,7 +586,6 @@ class Request:
|
|
|
583
586
|
|
|
584
587
|
def __post_init__(self) -> None:
|
|
585
588
|
super().__post_init__()
|
|
586
|
-
_Validate.symbol(self.symbol, "Flush request")
|
|
587
589
|
|
|
588
590
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
589
591
|
class FlushAll(Base.Request):
|
|
@@ -781,7 +783,6 @@ class Strategy:
|
|
|
781
783
|
|
|
782
784
|
def __post_init__(self) -> None:
|
|
783
785
|
super().__post_init__()
|
|
784
|
-
_Validate.symbol(self.symbol, "Strategy.SymbolRelease")
|
|
785
786
|
|
|
786
787
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
787
788
|
class SymbolAssignment(Base.Strategy):
|
|
@@ -803,8 +804,6 @@ class Strategy:
|
|
|
803
804
|
|
|
804
805
|
def __post_init__(self) -> None:
|
|
805
806
|
super().__post_init__()
|
|
806
|
-
for symbol in self.symbol_list:
|
|
807
|
-
_Validate.symbol(symbol, "Strategy.SymbolAssignment")
|
|
808
807
|
|
|
809
808
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
810
809
|
class StopTrading(Base.Strategy):
|
|
@@ -813,6 +812,7 @@ class Strategy:
|
|
|
813
812
|
|
|
814
813
|
Attributes:
|
|
815
814
|
shutdown_mode (models.StrategyShutdownMode): Shutdown mode to use.
|
|
815
|
+
Defaults to `SOFT`.
|
|
816
816
|
|
|
817
817
|
Examples:
|
|
818
818
|
>>> from onesecondtrader.messaging import events
|
|
@@ -824,6 +824,32 @@ class Strategy:
|
|
|
824
824
|
|
|
825
825
|
shutdown_mode: models.StrategyShutdownMode
|
|
826
826
|
|
|
827
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
828
|
+
class StopTradingSymbol(Base.Strategy):
|
|
829
|
+
"""
|
|
830
|
+
Event to indicate a strategy should stop trading a symbol.
|
|
831
|
+
|
|
832
|
+
Attributes:
|
|
833
|
+
symbol (str): Symbol to stop trading.
|
|
834
|
+
shutdown_mode (models.SymbolShutdownMode): Shutdown mode to use.
|
|
835
|
+
Defaults to `SOFT`.
|
|
836
|
+
|
|
837
|
+
Examples:
|
|
838
|
+
>>> from onesecondtrader.messaging import events
|
|
839
|
+
>>> event = events.Strategy.StopTradingSymbol(
|
|
840
|
+
... strategy=my_strategy,
|
|
841
|
+
... symbol="AAPL",
|
|
842
|
+
... shutdown_mode=models.SymbolShutdownMode.HARD,
|
|
843
|
+
... )
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
symbol: str
|
|
847
|
+
shutdown_mode: models.SymbolShutdownMode = models.SymbolShutdownMode.SOFT
|
|
848
|
+
|
|
849
|
+
def __post_init__(self) -> None:
|
|
850
|
+
super().__post_init__()
|
|
851
|
+
_Validate.symbol(self.symbol, f"StopTradingSymbol {self.symbol}")
|
|
852
|
+
|
|
827
853
|
|
|
828
854
|
class _Validate:
|
|
829
855
|
"""Internal validation utilities for events."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.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
|
|
@@ -3,8 +3,8 @@ onesecondtrader/brokers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
3
3
|
onesecondtrader/brokers/base_broker.py,sha256=PtLyFEXY5VisnFqJabOkRGEsSS05SUSTc7JIAzk-OA8,2948
|
|
4
4
|
onesecondtrader/brokers/simulated_broker.py,sha256=ptbDkGG7NDKpqPn5ZkthALI2p533J9twS9hDQCaMeOY,242
|
|
5
5
|
onesecondtrader/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
onesecondtrader/core/models.py,sha256=
|
|
7
|
-
onesecondtrader/core/portfolio.py,sha256=
|
|
6
|
+
onesecondtrader/core/models.py,sha256=zpjPMDVfPpWJv9NtLdK3IWXs11B_uEluGbX7u1PTKRw,5140
|
|
7
|
+
onesecondtrader/core/portfolio.py,sha256=gJvDKBm3J59SOybJpe77Ie3XW5eLndeArrP00wWefPc,8000
|
|
8
8
|
onesecondtrader/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
onesecondtrader/datafeeds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
onesecondtrader/datafeeds/base_datafeed.py,sha256=WViw7tzsVoZku-V-DxbqKSjNPkvaiA8G-J5Rs9eKKn8,1299
|
|
@@ -14,14 +14,14 @@ onesecondtrader/indicators/base_indicator.py,sha256=eGv5_WYOSsuLXX8MbnyE3_Y8owH-
|
|
|
14
14
|
onesecondtrader/indicators/moving_averages.py,sha256=ddZy640Z2aVgeiZ4SFRWsHDFaOBCW7u3mqBmc1wZrmQ,4678
|
|
15
15
|
onesecondtrader/messaging/__init__.py,sha256=8LMFnw7KsnctDxyC8ZybDHgcdMB8fSy56Fad9Ozj6Bw,243
|
|
16
16
|
onesecondtrader/messaging/eventbus.py,sha256=sEp5ebYNRHiqTRXaTqytZ2PV2wKDXj5NlWNi1OKn2_4,19447
|
|
17
|
-
onesecondtrader/messaging/events.py,sha256=
|
|
17
|
+
onesecondtrader/messaging/events.py,sha256=Aawuu77WG9h9dXGATQxEwiGwrFXEyG8dys324xRO7JA,28104
|
|
18
18
|
onesecondtrader/monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
onesecondtrader/monitoring/console.py,sha256=1mrojXkyL4ro7ebkvDMGNQiCL-93WEylRuwnfmEKzVs,299
|
|
20
20
|
onesecondtrader/monitoring/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
onesecondtrader/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
onesecondtrader/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
onesecondtrader/strategies/base_strategy.py,sha256=chmJyX8jVe-H24zmFDKeqClrGv-EJFtBlKzLcQe5mmM,1650
|
|
24
|
-
onesecondtrader-0.
|
|
25
|
-
onesecondtrader-0.
|
|
26
|
-
onesecondtrader-0.
|
|
27
|
-
onesecondtrader-0.
|
|
24
|
+
onesecondtrader-0.15.0.dist-info/METADATA,sha256=ZGwVcpzLfs6zRdGukXh_4m-xE4O5b4VwO-p0L4yC4Dg,9638
|
|
25
|
+
onesecondtrader-0.15.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
26
|
+
onesecondtrader-0.15.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
27
|
+
onesecondtrader-0.15.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|