onesecondtrader 0.15.0__py3-none-any.whl → 0.16.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 +0 -12
- onesecondtrader/indicators.py +102 -0
- onesecondtrader/ontology.py +18 -0
- {onesecondtrader-0.15.0.dist-info → onesecondtrader-0.16.0.dist-info}/METADATA +2 -1
- onesecondtrader-0.16.0.dist-info/RECORD +7 -0
- onesecondtrader/brokers/__init__.py +0 -0
- onesecondtrader/brokers/base_broker.py +0 -99
- onesecondtrader/brokers/simulated_broker.py +0 -10
- onesecondtrader/core/__init__.py +0 -0
- onesecondtrader/core/models.py +0 -204
- onesecondtrader/core/portfolio.py +0 -177
- onesecondtrader/core/py.typed +0 -0
- onesecondtrader/datafeeds/__init__.py +0 -0
- onesecondtrader/datafeeds/base_datafeed.py +0 -54
- onesecondtrader/datafeeds/csv_datafeed.py +0 -297
- onesecondtrader/indicators/__init__.py +0 -0
- onesecondtrader/indicators/base_indicator.py +0 -136
- onesecondtrader/indicators/moving_averages.py +0 -132
- onesecondtrader/messaging/__init__.py +0 -9
- onesecondtrader/messaging/eventbus.py +0 -499
- onesecondtrader/messaging/events.py +0 -894
- onesecondtrader/monitoring/__init__.py +0 -0
- onesecondtrader/monitoring/console.py +0 -14
- onesecondtrader/monitoring/py.typed +0 -0
- onesecondtrader/py.typed +0 -0
- onesecondtrader/strategies/__init__.py +0 -0
- onesecondtrader/strategies/base_strategy.py +0 -46
- onesecondtrader-0.15.0.dist-info/RECORD +0 -27
- {onesecondtrader-0.15.0.dist-info → onesecondtrader-0.16.0.dist-info}/WHEEL +0 -0
- {onesecondtrader-0.15.0.dist-info → onesecondtrader-0.16.0.dist-info}/licenses/LICENSE +0 -0
onesecondtrader/__init__.py
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OneSecondTrader's library of pre-built indicators.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import enum
|
|
7
|
+
import numpy as np
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
from collections import deque
|
|
11
|
+
from onesecondtrader.ontology import Bar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseIndicator(abc.ABC):
|
|
15
|
+
"""
|
|
16
|
+
Base class for indicators. Subclasses must set the `name` property and implement
|
|
17
|
+
the `_compute_indicator()` method. See `SimpleMovingAverage` for an example.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, max_history: int = 100) -> None:
|
|
21
|
+
self._lock = threading.Lock()
|
|
22
|
+
self._history: deque[float] = deque(maxlen=max(1, int(max_history)))
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@abc.abstractmethod
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def update(self, incoming_bar: Bar) -> None:
|
|
30
|
+
_latest_value: float = self._compute_indicator(incoming_bar)
|
|
31
|
+
with self._lock:
|
|
32
|
+
self._history.append(_latest_value)
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def _compute_indicator(self, incoming_bar: Bar) -> float:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def latest(self) -> float:
|
|
40
|
+
with self._lock:
|
|
41
|
+
return self._history[-1] if self._history else np.nan
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def history(self) -> deque[float]:
|
|
45
|
+
return self._history
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InputSource(enum.Enum):
|
|
49
|
+
"""
|
|
50
|
+
Enum of supported input sources for indicators. Indicators with a `input_source`
|
|
51
|
+
parameter can be configured to use one of these sources for their calculations.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
OPEN = enum.auto()
|
|
55
|
+
HIGH = enum.auto()
|
|
56
|
+
LOW = enum.auto()
|
|
57
|
+
CLOSE = enum.auto()
|
|
58
|
+
VOLUME = enum.auto()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SimpleMovingAverage(BaseIndicator):
|
|
62
|
+
"""
|
|
63
|
+
Simple Moving Average (SMA) indicator. Can be configured to use different input
|
|
64
|
+
sources (see `InputSource` enum, default is `InputSource.CLOSE`).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
period: int = 200,
|
|
70
|
+
max_history: int = 100,
|
|
71
|
+
input_source: InputSource = InputSource.CLOSE,
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__(max_history=max_history)
|
|
74
|
+
self.period: int = max(1, int(period))
|
|
75
|
+
self.input_source: InputSource = input_source
|
|
76
|
+
self._window: deque[float] = deque(maxlen=self.period)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def name(self) -> str:
|
|
80
|
+
return f"SMA_{self.period}_{self.input_source.name}"
|
|
81
|
+
|
|
82
|
+
def _compute_indicator(self, bar: Bar) -> float:
|
|
83
|
+
value: float = self._extract_input(bar)
|
|
84
|
+
self._window.append(value)
|
|
85
|
+
if len(self._window) < self.period:
|
|
86
|
+
return np.nan
|
|
87
|
+
return sum(self._window) / self.period
|
|
88
|
+
|
|
89
|
+
def _extract_input(self, bar: Bar) -> float:
|
|
90
|
+
match self.input_source:
|
|
91
|
+
case InputSource.OPEN:
|
|
92
|
+
return float(bar.open)
|
|
93
|
+
case InputSource.HIGH:
|
|
94
|
+
return float(bar.high)
|
|
95
|
+
case InputSource.LOW:
|
|
96
|
+
return float(bar.low)
|
|
97
|
+
case InputSource.CLOSE:
|
|
98
|
+
return float(bar.close)
|
|
99
|
+
case InputSource.VOLUME:
|
|
100
|
+
return float(bar.volume)
|
|
101
|
+
case _:
|
|
102
|
+
return float(bar.close)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain-specific data models that are used system-wide.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(slots=True)
|
|
9
|
+
class Bar:
|
|
10
|
+
"""
|
|
11
|
+
Data model for OHLCV bar data.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
open: float
|
|
15
|
+
high: float
|
|
16
|
+
low: float
|
|
17
|
+
close: float
|
|
18
|
+
volume: int
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: matplotlib (>=3.10.7,<4.0.0)
|
|
14
15
|
Requires-Dist: pandas (>=2.3.1,<3.0.0)
|
|
15
16
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
onesecondtrader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
onesecondtrader/indicators.py,sha256=nV7EFlOnNWxdU6-lb4rxvzjfdRIXkS7P-tyH08cSrKM,2967
|
|
3
|
+
onesecondtrader/ontology.py,sha256=z1fipmt6Rh8nS1Z-M3ln197TWPWSvt6rkBQXkdXhekM,263
|
|
4
|
+
onesecondtrader-0.16.0.dist-info/METADATA,sha256=oU16PF9yR3neuhhQNZbE64ZcSdBAnr4j1-_wecDwDkQ,9682
|
|
5
|
+
onesecondtrader-0.16.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
6
|
+
onesecondtrader-0.16.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
7
|
+
onesecondtrader-0.16.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
from onesecondtrader import messaging
|
|
3
|
-
from onesecondtrader.messaging import events
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class BaseBroker(abc.ABC):
|
|
7
|
-
def __init__(self, event_bus: messaging.EventBus | None = None):
|
|
8
|
-
self.event_bus: messaging.EventBus = (
|
|
9
|
-
event_bus if event_bus else messaging.system_event_bus
|
|
10
|
-
)
|
|
11
|
-
self._is_connected: bool = False
|
|
12
|
-
|
|
13
|
-
def connect(self) -> bool:
|
|
14
|
-
if self._is_connected:
|
|
15
|
-
return True
|
|
16
|
-
self._subscribe_to_events()
|
|
17
|
-
self._is_connected = True
|
|
18
|
-
return True
|
|
19
|
-
|
|
20
|
-
def disconnect(self) -> None:
|
|
21
|
-
if not self._is_connected:
|
|
22
|
-
return
|
|
23
|
-
try:
|
|
24
|
-
self.event_bus.unsubscribe(events.System.Shutdown, self.on_system_shutdown)
|
|
25
|
-
finally:
|
|
26
|
-
self._is_connected = False
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def is_connected(self) -> bool:
|
|
30
|
-
return self._is_connected
|
|
31
|
-
|
|
32
|
-
def _subscribe_to_events(self) -> None:
|
|
33
|
-
"""
|
|
34
|
-
Subscribe to relevant events from the event bus.
|
|
35
|
-
"""
|
|
36
|
-
self.event_bus.subscribe(events.System.Shutdown, self.on_system_shutdown)
|
|
37
|
-
self.event_bus.subscribe(events.Strategy.SymbolRelease, self.on_symbol_release)
|
|
38
|
-
|
|
39
|
-
def on_system_shutdown(self, event: events.Base.Event) -> None:
|
|
40
|
-
"""
|
|
41
|
-
Handle system shutdown events (ignore unrelated events).
|
|
42
|
-
"""
|
|
43
|
-
if not isinstance(event, events.System.Shutdown):
|
|
44
|
-
return
|
|
45
|
-
# Default no-op
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
def on_symbol_release(self, event: events.Base.Event) -> None:
|
|
49
|
-
"""
|
|
50
|
-
Handle portfolio symbol release events (ignore unrelated events).
|
|
51
|
-
Intended for brokers to perform any symbol-specific cleanup if necessary.
|
|
52
|
-
Default implementation is a no-op.
|
|
53
|
-
"""
|
|
54
|
-
if not isinstance(event, events.Strategy.SymbolRelease):
|
|
55
|
-
return
|
|
56
|
-
# Default no-op
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
def on_request_market_order(self, event: events.Request.MarketOrder) -> None:
|
|
60
|
-
"""
|
|
61
|
-
Handle market order requests.
|
|
62
|
-
"""
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
def on_request_limit_order(self, event: events.Request.LimitOrder) -> None:
|
|
66
|
-
"""
|
|
67
|
-
Handle limit order requests.
|
|
68
|
-
"""
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
def on_request_stop_order(self, event: events.Request.StopOrder) -> None:
|
|
72
|
-
"""
|
|
73
|
-
Handle stop order requests.
|
|
74
|
-
"""
|
|
75
|
-
pass
|
|
76
|
-
|
|
77
|
-
def on_request_stop_limit_order(self, event: events.Request.StopLimitOrder) -> None:
|
|
78
|
-
"""
|
|
79
|
-
Handle stop limit order requests.
|
|
80
|
-
"""
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
|
-
def on_request_cancel_order(self, event: events.Request.CancelOrder) -> None:
|
|
84
|
-
"""
|
|
85
|
-
Handle cancel order requests.
|
|
86
|
-
"""
|
|
87
|
-
pass
|
|
88
|
-
|
|
89
|
-
def on_request_flush_symbol(self, event: events.Request.FlushSymbol) -> None:
|
|
90
|
-
"""
|
|
91
|
-
Handle flush symbol requests.
|
|
92
|
-
"""
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
def on_request_flush_all(self, event: events.Request.FlushAll) -> None:
|
|
96
|
-
"""
|
|
97
|
-
Handle flush all requests.
|
|
98
|
-
"""
|
|
99
|
-
pass
|
onesecondtrader/core/__init__.py
DELETED
|
File without changes
|
onesecondtrader/core/models.py
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
import enum
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@dataclass(frozen=True, slots=True)
|
|
6
|
-
class Bar:
|
|
7
|
-
"""
|
|
8
|
-
Class for representing a OHLC(V) bar of market data.
|
|
9
|
-
|
|
10
|
-
Attributes:
|
|
11
|
-
open (float): Open price
|
|
12
|
-
high (float): High price
|
|
13
|
-
low (float): Low price
|
|
14
|
-
close (float): Close price
|
|
15
|
-
volume (int | None): Volume
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
open: float
|
|
19
|
-
high: float
|
|
20
|
-
low: float
|
|
21
|
-
close: float
|
|
22
|
-
volume: int | None = None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class Side(enum.Enum):
|
|
26
|
-
"""
|
|
27
|
-
Enum for order sides.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
BUY = enum.auto()
|
|
31
|
-
SELL = enum.auto()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class TimeInForce(enum.Enum):
|
|
35
|
-
"""
|
|
36
|
-
Order time-in-force specifications.
|
|
37
|
-
|
|
38
|
-
**Attributes:**
|
|
39
|
-
|
|
40
|
-
| Enum | Value | Description |
|
|
41
|
-
|------|-------|-------------|
|
|
42
|
-
| `DAY` | `enum.auto()` | Valid until end of trading day |
|
|
43
|
-
| `FOK` | `enum.auto()` | Fill entire order immediately or cancel (Fill-or-Kill) |
|
|
44
|
-
| `GTC` | `enum.auto()` | Active until explicitly cancelled (Good-Till-Cancelled) |
|
|
45
|
-
| `GTD` | `enum.auto()` | Active until specified date (Good-Till-Date) |
|
|
46
|
-
| `IOC` | `enum.auto()` | Execute available quantity immediately, cancel rest (Immediate-or-Cancel) |
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
DAY = enum.auto()
|
|
50
|
-
FOK = enum.auto()
|
|
51
|
-
GTC = enum.auto()
|
|
52
|
-
GTD = enum.auto()
|
|
53
|
-
IOC = enum.auto()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class OrderType(enum.Enum):
|
|
57
|
-
"""
|
|
58
|
-
Enum for order types.
|
|
59
|
-
|
|
60
|
-
**Attributes:**
|
|
61
|
-
|
|
62
|
-
| Enum | Value | Description |
|
|
63
|
-
|------|-------|-------------|
|
|
64
|
-
| `MARKET` | `enum.auto()` | Market order |
|
|
65
|
-
| `LIMIT` | `enum.auto()` | Limit order |
|
|
66
|
-
| `STOP` | `enum.auto()` | Stop order |
|
|
67
|
-
| `STOP_LIMIT` | `enum.auto()` | Stop-limit order |
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
MARKET = enum.auto()
|
|
71
|
-
LIMIT = enum.auto()
|
|
72
|
-
STOP = enum.auto()
|
|
73
|
-
STOP_LIMIT = enum.auto()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class OrderLifecycleState(enum.Enum):
|
|
77
|
-
"""
|
|
78
|
-
Enum for order lifecycle states.
|
|
79
|
-
|
|
80
|
-
**Attributes:**
|
|
81
|
-
|
|
82
|
-
| Enum | Value | Description |
|
|
83
|
-
|------|-------|-------------|
|
|
84
|
-
| `PENDING` | `enum.auto()` | Order has been submitted, but not yet acknowledged by the brokers |
|
|
85
|
-
| `OPEN` | `enum.auto()` | Order has been acknowledged by the brokers, but not yet filled or cancelled |
|
|
86
|
-
| `FILLED` | `enum.auto()` | Order has been filled |
|
|
87
|
-
| `CANCELLED` | `enum.auto()` | Order has been cancelled |
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
PENDING = enum.auto()
|
|
91
|
-
OPEN = enum.auto()
|
|
92
|
-
PARTIALLY_FILLED = enum.auto()
|
|
93
|
-
FILLED = enum.auto()
|
|
94
|
-
CANCELLED = enum.auto()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class OrderRejectionReason(enum.Enum):
|
|
98
|
-
"""
|
|
99
|
-
Enum for order rejection reasons.
|
|
100
|
-
|
|
101
|
-
**Attributes:**
|
|
102
|
-
|
|
103
|
-
| Enum | Value | Description |
|
|
104
|
-
|------|-------|-------------|
|
|
105
|
-
| `UNKNOWN` | `enum.auto()` | Unknown reason |
|
|
106
|
-
| `NEGATIVE_QUANTITY` | `enum.auto()` | Negative quantity |
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
UNKNOWN = enum.auto()
|
|
110
|
-
NEGATIVE_QUANTITY = enum.auto()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class RecordType(enum.Enum):
|
|
114
|
-
"""
|
|
115
|
-
Enum for Databento record types.
|
|
116
|
-
|
|
117
|
-
**Attributes:**
|
|
118
|
-
|
|
119
|
-
| Enum | Value | Description |
|
|
120
|
-
|------|-------|-------------|
|
|
121
|
-
| `OHLCV_1S` | `32` | 1-second bars |
|
|
122
|
-
| `OHLCV_1M` | `33` | 1-minute bars |
|
|
123
|
-
| `OHLCV_1H` | `34` | 1-hour bars |
|
|
124
|
-
| `OHLCV_1D` | `35` | 1-day bars |
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
OHLCV_1S = 32
|
|
128
|
-
OHLCV_1M = 33
|
|
129
|
-
OHLCV_1H = 34
|
|
130
|
-
OHLCV_1D = 35
|
|
131
|
-
|
|
132
|
-
@classmethod
|
|
133
|
-
def to_string(cls, rtype: int) -> str:
|
|
134
|
-
match rtype:
|
|
135
|
-
case cls.OHLCV_1S.value:
|
|
136
|
-
return "1-second bars"
|
|
137
|
-
case cls.OHLCV_1M.value:
|
|
138
|
-
return "1-minute bars"
|
|
139
|
-
case cls.OHLCV_1H.value:
|
|
140
|
-
return "1-hour bars"
|
|
141
|
-
case cls.OHLCV_1D.value:
|
|
142
|
-
return "daily bars"
|
|
143
|
-
case _:
|
|
144
|
-
return f"unknown ({rtype})"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
class XMAMode(enum.Enum):
|
|
148
|
-
"""
|
|
149
|
-
Enum for moving average modes.
|
|
150
|
-
|
|
151
|
-
**Attributes:**
|
|
152
|
-
|
|
153
|
-
| Enum | Value | Description |
|
|
154
|
-
|------|-------|-------------|
|
|
155
|
-
| `OPEN` | `enum.auto()` | Open price |
|
|
156
|
-
| `HIGH` | `enum.auto()` | High price |
|
|
157
|
-
| `LOW` | `enum.auto()` | Low price |
|
|
158
|
-
| `CLOSE` | `enum.auto()` | Close price |
|
|
159
|
-
| `TYPICAL_PRICE` | `enum.auto()` | Typical price ((H+ L + C) / 3) |
|
|
160
|
-
| `WEIGHTED_CLOSE` | `enum.auto()` | Weighted close price ((H + L + 2*C) / 4) |
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
OPEN = enum.auto()
|
|
164
|
-
HIGH = enum.auto()
|
|
165
|
-
LOW = enum.auto()
|
|
166
|
-
CLOSE = enum.auto()
|
|
167
|
-
TYPICAL_PRICE = enum.auto()
|
|
168
|
-
WEIGHTED_CLOSE = enum.auto()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class Position:
|
|
172
|
-
pass
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
class StrategyShutdownMode(enum.Enum):
|
|
176
|
-
"""
|
|
177
|
-
Enum for strategy shutdown modes.
|
|
178
|
-
|
|
179
|
-
**Attributes:**
|
|
180
|
-
|
|
181
|
-
| Enum | Value | Description |
|
|
182
|
-
|------|-------|-------------|
|
|
183
|
-
| `SOFT` | `enum.auto()` | Do not open new positions; wait until current positions close naturally |
|
|
184
|
-
| `HARD` | `enum.auto()` | Close all positions immediately with market orders |
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
SOFT = enum.auto()
|
|
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,177 +0,0 @@
|
|
|
1
|
-
import threading
|
|
2
|
-
|
|
3
|
-
from onesecondtrader import messaging
|
|
4
|
-
from onesecondtrader.messaging import events
|
|
5
|
-
from onesecondtrader.strategies import base_strategy
|
|
6
|
-
from onesecondtrader.monitoring import console
|
|
7
|
-
from onesecondtrader.core import models
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Portfolio:
|
|
11
|
-
def __init__(self, event_bus: messaging.EventBus | None = None):
|
|
12
|
-
"""
|
|
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.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
event_bus (messaging.EventBus | None): Event bus to use; defaults to
|
|
20
|
-
messaging.system_event_bus when None.
|
|
21
|
-
|
|
22
|
-
Attributes:
|
|
23
|
-
self._lock (threading.Lock): Lock for thread-safe operations.
|
|
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.
|
|
28
|
-
"""
|
|
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
|
|
35
|
-
self.event_bus: messaging.EventBus = (
|
|
36
|
-
event_bus if event_bus else messaging.system_event_bus
|
|
37
|
-
)
|
|
38
|
-
self.event_bus.subscribe(events.Strategy.SymbolRelease, self.on_symbol_release)
|
|
39
|
-
|
|
40
|
-
# ------------------------------------------------------------------------------
|
|
41
|
-
# INITIALIZE SYMBOLS TO STRATEGY REGISTRY
|
|
42
|
-
self.symbols_to_strategy: dict[str, base_strategy.Strategy] = {}
|
|
43
|
-
|
|
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.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
event (messaging.events.Base.Event): Symbol release event.
|
|
51
|
-
"""
|
|
52
|
-
# ------------------------------------------------------------------------------
|
|
53
|
-
# IGNORE UNRELATED EVENT TYPES
|
|
54
|
-
if not isinstance(event, events.Strategy.SymbolRelease):
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
# ------------------------------------------------------------------------------
|
|
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__)}"
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
def assign_symbols(
|
|
74
|
-
self, strategy_instance: base_strategy.Strategy, symbols: list[str]
|
|
75
|
-
) -> bool:
|
|
76
|
-
"""
|
|
77
|
-
Assign a list of symbols to a strategy if no conflicts exist and notify the
|
|
78
|
-
strategy of the assignment.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
strategy_instance (base_strategy.Strategy): Strategy instance to assign
|
|
82
|
-
symbols to.
|
|
83
|
-
symbols (list[str]): List of symbols to assign.
|
|
84
|
-
"""
|
|
85
|
-
# ------------------------------------------------------------------------------
|
|
86
|
-
# VALIDATE THAT INSTANCE IS A SUBCLASS OF base_strategy.Strategy
|
|
87
|
-
if not isinstance(strategy_instance, base_strategy.Strategy):
|
|
88
|
-
console.logger.error("assign_symbols: strategy must inherit from Strategy")
|
|
89
|
-
return False
|
|
90
|
-
|
|
91
|
-
# ------------------------------------------------------------------------------
|
|
92
|
-
# CHECK FOR CONFLICTS
|
|
93
|
-
non_conflicting: list[str] = []
|
|
94
|
-
conflicting: list[str] = []
|
|
95
|
-
with self._lock:
|
|
96
|
-
for symbol in symbols:
|
|
97
|
-
owner = self.symbols_to_strategy.get(symbol)
|
|
98
|
-
if owner is None:
|
|
99
|
-
non_conflicting.append(symbol)
|
|
100
|
-
else:
|
|
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
|
|
125
|
-
|
|
126
|
-
def unassign_symbols(
|
|
127
|
-
self,
|
|
128
|
-
symbols: list[str],
|
|
129
|
-
shutdown_mode: models.SymbolShutdownMode = models.SymbolShutdownMode.SOFT,
|
|
130
|
-
) -> bool:
|
|
131
|
-
"""
|
|
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.
|
|
140
|
-
|
|
141
|
-
Args:
|
|
142
|
-
symbols (list[str]): List of symbols to unassign.
|
|
143
|
-
shutdown_mode (models.SymbolShutdownMode): Shutdown mode to use. Defaults
|
|
144
|
-
to `models.SymbolShutdownMode.SOFT`.
|
|
145
|
-
"""
|
|
146
|
-
# ------------------------------------------------------------------------------
|
|
147
|
-
# CHECK THAT SYMBOLS ARE REGISTERED
|
|
148
|
-
conflicting: list[str] = []
|
|
149
|
-
with self._lock:
|
|
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
|
-
)
|
|
159
|
-
return False
|
|
160
|
-
else:
|
|
161
|
-
# ----------------------------------------------------------------------
|
|
162
|
-
# PUBLISH STOP TRADING SYMBOL EVENT FOR EACH SYMBOL
|
|
163
|
-
for symbol in symbols:
|
|
164
|
-
# noinspection PyArgumentList
|
|
165
|
-
self.event_bus.publish(
|
|
166
|
-
events.Strategy.StopTradingSymbol(
|
|
167
|
-
strategy=self.symbols_to_strategy[symbol],
|
|
168
|
-
symbol=symbol,
|
|
169
|
-
shutdown_mode=shutdown_mode,
|
|
170
|
-
)
|
|
171
|
-
)
|
|
172
|
-
console.logger.info(
|
|
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}"
|
|
176
|
-
)
|
|
177
|
-
return True
|
onesecondtrader/core/py.typed
DELETED
|
File without changes
|
|
File without changes
|