onesecondtrader 0.14.0__py3-none-any.whl → 0.49.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/events/__init__.py +8 -0
- onesecondtrader/events/base.py +21 -0
- onesecondtrader/events/market/__init__.py +7 -0
- onesecondtrader/events/market/bar_processed.py +29 -0
- onesecondtrader/events/market/bar_received.py +34 -0
- onesecondtrader/events/orders/__init__.py +9 -0
- onesecondtrader/events/orders/base.py +30 -0
- onesecondtrader/events/orders/expirations.py +23 -0
- onesecondtrader/events/orders/fills.py +41 -0
- onesecondtrader/events/requests/__init__.py +11 -0
- onesecondtrader/events/requests/base.py +25 -0
- onesecondtrader/events/requests/order_cancellation.py +21 -0
- onesecondtrader/events/requests/order_modification.py +26 -0
- onesecondtrader/events/requests/order_submission.py +35 -0
- onesecondtrader/events/responses/__init__.py +14 -0
- onesecondtrader/events/responses/base.py +26 -0
- onesecondtrader/events/responses/cancellations.py +42 -0
- onesecondtrader/events/responses/modifications.py +43 -0
- onesecondtrader/events/responses/orders.py +42 -0
- onesecondtrader/indicators/__init__.py +17 -0
- onesecondtrader/indicators/base.py +142 -0
- onesecondtrader/indicators/market_fields.py +166 -0
- onesecondtrader/indicators/moving_averages.py +78 -106
- onesecondtrader/models/__init__.py +23 -0
- onesecondtrader/models/bar_fields.py +23 -0
- onesecondtrader/models/bar_period.py +21 -0
- onesecondtrader/models/order_types.py +21 -0
- onesecondtrader/models/rejection_reasons.py +48 -0
- onesecondtrader/models/trade_sides.py +20 -0
- {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/METADATA +9 -2
- onesecondtrader-0.49.0.dist-info/RECORD +34 -0
- {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/WHEEL +1 -1
- onesecondtrader/core/__init__.py +0 -0
- onesecondtrader/core/models.py +0 -188
- 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/base_indicator.py +0 -136
- onesecondtrader/messaging/__init__.py +0 -9
- onesecondtrader/messaging/eventbus.py +0 -499
- onesecondtrader/messaging/events.py +0 -800
- onesecondtrader/monitoring/__init__.py +0 -0
- onesecondtrader/monitoring/console.py +0 -14
- onesecondtrader/monitoring/py.typed +0 -0
- onesecondtrader/py.typed +0 -0
- onesecondtrader-0.14.0.dist-info/RECORD +0 -21
- {onesecondtrader-0.14.0.dist-info → onesecondtrader-0.49.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.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,8 +11,15 @@ 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: databento (>=0.69.0,<0.70.0)
|
|
15
|
+
Requires-Dist: fastapi (>=0.128.0,<0.129.0)
|
|
16
|
+
Requires-Dist: ib-async (>=2.1.0,<3.0.0)
|
|
17
|
+
Requires-Dist: matplotlib (>=3.10.7,<4.0.0)
|
|
18
|
+
Requires-Dist: mplfinance (>=0.12.10b0,<0.13.0)
|
|
14
19
|
Requires-Dist: pandas (>=2.3.1,<3.0.0)
|
|
15
20
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
21
|
+
Requires-Dist: tqdm (>=4.67.1,<5.0.0)
|
|
22
|
+
Requires-Dist: uvicorn (>=0.40.0,<0.41.0)
|
|
16
23
|
Description-Content-Type: text/markdown
|
|
17
24
|
|
|
18
25
|
# OneSecondTrader
|
|
@@ -110,7 +117,7 @@ graph TD
|
|
|
110
117
|
B["<b>Code Quality Checks</b><br/>• Ruff Check & Format<br/>• MyPy Type Checking<br/>• Tests & Doctests"]
|
|
111
118
|
C["<b>Security Checks</b><br/>• Gitleaks Secret Detection"]
|
|
112
119
|
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"]
|
|
113
|
-
E["<b>Generate
|
|
120
|
+
E["<b>Generate Reference Documentation</b> via <kbd>scripts/generate_reference_docs.py</kbd><br/>• Auto-generate docs<br/>• Stage changes"]
|
|
114
121
|
end
|
|
115
122
|
B --> C --> D --> E
|
|
116
123
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
onesecondtrader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
onesecondtrader/events/__init__.py,sha256=1T7hJA6afxClEXvvnbXtHu9iMyhduRdJZWlg4ObWaKE,222
|
|
3
|
+
onesecondtrader/events/base.py,sha256=WpLo1bSKJe7Poh2IuDDCiBYZo9vE8mkq3cQlUpyTXsY,850
|
|
4
|
+
onesecondtrader/events/market/__init__.py,sha256=49z6maexBIDkAjIfkLbYzSZWEbyTpQ_HEEgT0eacrDo,132
|
|
5
|
+
onesecondtrader/events/market/bar_processed.py,sha256=NsCDGC21ykmioJoITspoAuz5mesKP0_hkM4UumWk1eU,1993
|
|
6
|
+
onesecondtrader/events/market/bar_received.py,sha256=_LJEZ7P-AfYQEIkXYNt-toO9_WG_XTS2Y86tb-qdOVU,1961
|
|
7
|
+
onesecondtrader/events/orders/__init__.py,sha256=IgVmiVUGMwDlFnL1ItAgvPwmkqEMJDLgQK8aJIKQ1Ic,164
|
|
8
|
+
onesecondtrader/events/orders/base.py,sha256=3zTY8JMkXiOV35k0tp6rZZ9X7pDN40Btm0SFT5Mq3nw,1650
|
|
9
|
+
onesecondtrader/events/orders/expirations.py,sha256=nW_28REmNNyxLpKzkAhwnC_mMqw00SqRediiZW8Zyz8,1465
|
|
10
|
+
onesecondtrader/events/orders/fills.py,sha256=IDSCR7__AAPWm69noV9TeE2C8LukZv-ju8yjDfgmA9w,2772
|
|
11
|
+
onesecondtrader/events/requests/__init__.py,sha256=PWVAjNdgHWZArWXz9HU7E-6ZgMdfCpn4wAJiM9a5q6E,325
|
|
12
|
+
onesecondtrader/events/requests/base.py,sha256=XXdiOy946hW3LmF7lMShi10kPtGftl928aPMDwEdnao,1084
|
|
13
|
+
onesecondtrader/events/requests/order_cancellation.py,sha256=X_ZMOx09gz0Hf8hvDE1OahqK1SoH4nw9IOMJIdbbTKs,1029
|
|
14
|
+
onesecondtrader/events/requests/order_modification.py,sha256=Tks2mXn1bkuOVLsk98tTk3E2CTld6SF1IwmkCvGvsc8,1538
|
|
15
|
+
onesecondtrader/events/requests/order_submission.py,sha256=ejLBEWJ7pzhXWcTncK1x_ZJfcaC21CnuEz00wvi1q-8,2155
|
|
16
|
+
onesecondtrader/events/responses/__init__.py,sha256=ihg3zxjygLi-sA6wIbsm163ic8346WiAVtztb6ONy_4,409
|
|
17
|
+
onesecondtrader/events/responses/base.py,sha256=tXOP3lvd6EqpC7NbfIJinUCI-KMz6xsR1YSQw5qNddk,1241
|
|
18
|
+
onesecondtrader/events/responses/cancellations.py,sha256=HWF15Flz2JNOaZvArAjl9jLAt1NrQxB3MASG-0A2Z2Y,2941
|
|
19
|
+
onesecondtrader/events/responses/modifications.py,sha256=ZQyehdwgAIg6HMmpQ5T_bWZ7yc4nfSeVwAGQ3T2O5-A,2968
|
|
20
|
+
onesecondtrader/events/responses/orders.py,sha256=enkIUYRw9G3bzAq-it20XvXBew2gLg1mAm44m-12bns,2786
|
|
21
|
+
onesecondtrader/indicators/__init__.py,sha256=i-syRqCdQdC2BLL-7bDBcdl2Mll1LgE7hi2GleCJ4HI,366
|
|
22
|
+
onesecondtrader/indicators/base.py,sha256=iGfgOj1B_LRGBeh7VjM5OjxoYnSxV7JZUP1C3O_dfmE,4653
|
|
23
|
+
onesecondtrader/indicators/market_fields.py,sha256=Znyii0egBkJT3CFlYhQ5ArRNidmBLF0TzgCr2AWu5CA,4306
|
|
24
|
+
onesecondtrader/indicators/moving_averages.py,sha256=Ej3Vg-K4Kf93J3MS6av2J8FRP1Kqx4G6yNZj1QfE9lU,3411
|
|
25
|
+
onesecondtrader/models/__init__.py,sha256=XWL6aNLwAA2JQMoqK2PY-_CwigV0ighx4zwGQVdmtCs,529
|
|
26
|
+
onesecondtrader/models/bar_fields.py,sha256=GnLBL08ueUr35w2dAbKwOBWrdBS98OC9r0T2NifwTH8,646
|
|
27
|
+
onesecondtrader/models/bar_period.py,sha256=J8ncVtcAxR52uD0nbC8Knds_GUP5wiuNj5rAKq4vv-4,475
|
|
28
|
+
onesecondtrader/models/order_types.py,sha256=SiJamarLQ7zkHzHLLbd86I_TeZrQJ4QEIMqNHj4dxXU,737
|
|
29
|
+
onesecondtrader/models/rejection_reasons.py,sha256=BToLmPholsP9GcLGZ874nQJpehZ1yB1SFyYqlf3AOwc,2127
|
|
30
|
+
onesecondtrader/models/trade_sides.py,sha256=Pf9BpxoUxqgKC_EKAExfSqgfIIK9NW-RpJES0XHRF-8,583
|
|
31
|
+
onesecondtrader-0.49.0.dist-info/METADATA,sha256=EFqtCI8r8EWYjn-l1LOkaWIsF0QrUZyq1UFA2WI56Dk,9951
|
|
32
|
+
onesecondtrader-0.49.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
33
|
+
onesecondtrader-0.49.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
34
|
+
onesecondtrader-0.49.0.dist-info/RECORD,,
|
onesecondtrader/core/__init__.py
DELETED
|
File without changes
|
onesecondtrader/core/models.py
DELETED
|
@@ -1,188 +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()
|
onesecondtrader/core/py.typed
DELETED
|
File without changes
|
|
File without changes
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides the base class for all datafeeds.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import abc
|
|
6
|
-
from onesecondtrader import messaging
|
|
7
|
-
from onesecondtrader.core import models
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class BaseDatafeed(abc.ABC):
|
|
11
|
-
"""
|
|
12
|
-
Base class for all datafeeds.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
def __init__(self, event_bus: messaging.EventBus | None = None):
|
|
16
|
-
self.event_bus: messaging.EventBus = (
|
|
17
|
-
event_bus if event_bus else messaging.system_event_bus
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
@abc.abstractmethod
|
|
21
|
-
def connect(self):
|
|
22
|
-
"""
|
|
23
|
-
Connect to the datafeed.
|
|
24
|
-
"""
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
@abc.abstractmethod
|
|
28
|
-
def watch(self, symbols: list[tuple[str, models.RecordType]]):
|
|
29
|
-
"""
|
|
30
|
-
Start watching symbols.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
symbols (list[tuple[str, models.TimeFrame]]): List of symbols to watch with
|
|
34
|
-
their respective timeframes.
|
|
35
|
-
"""
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
@abc.abstractmethod
|
|
39
|
-
def unwatch(self, symbols: list[tuple[str, models.RecordType]]):
|
|
40
|
-
"""
|
|
41
|
-
Stop watching symbols.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
symbols (list[tuple[str, models.TimeFrame]]): List of symbols to stop
|
|
45
|
-
watching with their respective timeframes.
|
|
46
|
-
"""
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
@abc.abstractmethod
|
|
50
|
-
def disconnect(self):
|
|
51
|
-
"""
|
|
52
|
-
Disconnect from the datafeed.
|
|
53
|
-
"""
|
|
54
|
-
pass
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides a CSV-based simulated live datafeed.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import pandas as pd
|
|
7
|
-
import threading
|
|
8
|
-
import time
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from dotenv import load_dotenv
|
|
11
|
-
from onesecondtrader.messaging import events, eventbus
|
|
12
|
-
from onesecondtrader.core import models
|
|
13
|
-
from onesecondtrader.monitoring import console
|
|
14
|
-
from onesecondtrader.datafeeds import base_datafeed
|
|
15
|
-
from pandas.io.parsers.readers import TextFileReader
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class CSVDatafeed(base_datafeed.BaseDatafeed):
|
|
19
|
-
"""
|
|
20
|
-
CSV-based simulated live datafeed.
|
|
21
|
-
|
|
22
|
-
Only one instance of any BaseDatafeed subclass can exist at a time.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
_instance = None
|
|
26
|
-
|
|
27
|
-
def __init__(
|
|
28
|
-
self,
|
|
29
|
-
event_bus: eventbus.EventBus,
|
|
30
|
-
csv_path: str | Path | None = None,
|
|
31
|
-
streaming_delay: float | None = None,
|
|
32
|
-
):
|
|
33
|
-
"""
|
|
34
|
-
Initialize CSV datafeed.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
event_bus: Event bus used to publish market data events.
|
|
38
|
-
csv_path: Optional path to CSV file. Overrides CSV_PATH env var.
|
|
39
|
-
streaming_delay: Optional delay in seconds between processing rows.
|
|
40
|
-
Overrides CSV_STREAMING_DELAY env var.
|
|
41
|
-
|
|
42
|
-
Attributes:
|
|
43
|
-
self.csv_path (Path | None): Path to CSV file.
|
|
44
|
-
self.data_iterator (TextFileReader | None): Iterator for reading CSV.
|
|
45
|
-
self._watched_symbols (set[tuple[str, models.RecordType]]): Set of
|
|
46
|
-
symbols and record types currently being watched.
|
|
47
|
-
self._streaming_thread (threading.Thread | None): Background thread
|
|
48
|
-
for streaming data.
|
|
49
|
-
self._symbols_lock (threading.Lock): Lock to protect _watched_symbols
|
|
50
|
-
from concurrent access.
|
|
51
|
-
self._streaming_delay (float): Delay in seconds between processing
|
|
52
|
-
CSV rows (from CSV_STREAMING_DELAY env var, set in connect()).
|
|
53
|
-
self._init_csv_path (str | Path | None): CSV path provided during
|
|
54
|
-
initialization.
|
|
55
|
-
self._init_streaming_delay (float | None): Streaming delay provided
|
|
56
|
-
during initialization.
|
|
57
|
-
"""
|
|
58
|
-
if CSVDatafeed._instance is not None:
|
|
59
|
-
console.logger.warning(
|
|
60
|
-
f"Only one BaseDatafeed instance allowed. "
|
|
61
|
-
f"Current: {type(CSVDatafeed._instance).__name__}. "
|
|
62
|
-
f"Initialization failed."
|
|
63
|
-
)
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
super().__init__(event_bus)
|
|
67
|
-
CSVDatafeed._instance = self
|
|
68
|
-
|
|
69
|
-
self.csv_path: Path | None = None
|
|
70
|
-
self.data_iterator: TextFileReader | None = None
|
|
71
|
-
self._watched_symbols: set[tuple[str, models.RecordType]] = set()
|
|
72
|
-
self._stop_event = threading.Event()
|
|
73
|
-
self._streaming_thread: threading.Thread | None = None
|
|
74
|
-
self._symbols_lock: threading.Lock = threading.Lock()
|
|
75
|
-
self._streaming_delay: float = 0.0
|
|
76
|
-
|
|
77
|
-
self._init_csv_path: str | Path | None = csv_path
|
|
78
|
-
self._init_streaming_delay: float | None = streaming_delay
|
|
79
|
-
|
|
80
|
-
def connect(self):
|
|
81
|
-
"""
|
|
82
|
-
Connect to CSV file specified in .env file (CSV_PATH variable) and
|
|
83
|
-
create data iterator.
|
|
84
|
-
"""
|
|
85
|
-
load_dotenv()
|
|
86
|
-
|
|
87
|
-
if self._init_csv_path is not None:
|
|
88
|
-
csv_path_str = str(self._init_csv_path)
|
|
89
|
-
console.logger.info(f"Using CSV path from initialization: {csv_path_str}")
|
|
90
|
-
else:
|
|
91
|
-
csv_path_str = os.getenv("CSV_PATH")
|
|
92
|
-
if not csv_path_str:
|
|
93
|
-
console.logger.error(
|
|
94
|
-
"CSV_PATH not found in environment variables and not "
|
|
95
|
-
"provided in __init__. Either set CSV_PATH in .env file "
|
|
96
|
-
"or pass csv_path to CSVDatafeed()"
|
|
97
|
-
)
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
|
-
if self._init_streaming_delay is not None:
|
|
101
|
-
self._streaming_delay = self._init_streaming_delay
|
|
102
|
-
if self._streaming_delay < 0:
|
|
103
|
-
console.logger.warning(
|
|
104
|
-
f"Streaming delay cannot be negative "
|
|
105
|
-
f"({self._streaming_delay}), using default 0.0"
|
|
106
|
-
)
|
|
107
|
-
self._streaming_delay = 0.0
|
|
108
|
-
else:
|
|
109
|
-
console.logger.info(
|
|
110
|
-
f"CSV streaming delay set from initialization: "
|
|
111
|
-
f"{self._streaming_delay} seconds"
|
|
112
|
-
)
|
|
113
|
-
else:
|
|
114
|
-
streaming_delay_str = os.getenv("CSV_STREAMING_DELAY", "0.0")
|
|
115
|
-
try:
|
|
116
|
-
self._streaming_delay = float(streaming_delay_str)
|
|
117
|
-
if self._streaming_delay < 0:
|
|
118
|
-
console.logger.warning(
|
|
119
|
-
f"CSV_STREAMING_DELAY cannot be negative "
|
|
120
|
-
f"({self._streaming_delay}), using default 0.0"
|
|
121
|
-
)
|
|
122
|
-
self._streaming_delay = 0.0
|
|
123
|
-
else:
|
|
124
|
-
console.logger.info(
|
|
125
|
-
f"CSV streaming delay set from environment: "
|
|
126
|
-
f"{self._streaming_delay} seconds"
|
|
127
|
-
)
|
|
128
|
-
except ValueError:
|
|
129
|
-
console.logger.error(
|
|
130
|
-
f"Invalid CSV_STREAMING_DELAY value "
|
|
131
|
-
f"'{streaming_delay_str}', must be a number. "
|
|
132
|
-
f"Using default 0.0"
|
|
133
|
-
)
|
|
134
|
-
self._streaming_delay = 0.0
|
|
135
|
-
|
|
136
|
-
self.csv_path = Path(csv_path_str)
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
self.data_iterator = pd.read_csv(
|
|
140
|
-
self.csv_path,
|
|
141
|
-
usecols=[
|
|
142
|
-
"ts_event",
|
|
143
|
-
"rtype",
|
|
144
|
-
"open",
|
|
145
|
-
"high",
|
|
146
|
-
"low",
|
|
147
|
-
"close",
|
|
148
|
-
"volume",
|
|
149
|
-
"symbol",
|
|
150
|
-
],
|
|
151
|
-
dtype={
|
|
152
|
-
"ts_event": int,
|
|
153
|
-
"rtype": int,
|
|
154
|
-
"open": int,
|
|
155
|
-
"high": int,
|
|
156
|
-
"low": int,
|
|
157
|
-
"close": int,
|
|
158
|
-
"volume": int,
|
|
159
|
-
"symbol": str,
|
|
160
|
-
},
|
|
161
|
-
iterator=True,
|
|
162
|
-
chunksize=1,
|
|
163
|
-
)
|
|
164
|
-
console.logger.info(f"CSV datafeed connected to: {self.csv_path}")
|
|
165
|
-
self._stop_event.clear()
|
|
166
|
-
return True
|
|
167
|
-
|
|
168
|
-
except Exception as e:
|
|
169
|
-
console.logger.error(f"Failed to connect to CSV file {self.csv_path}: {e}")
|
|
170
|
-
return False
|
|
171
|
-
|
|
172
|
-
def watch(self, symbols):
|
|
173
|
-
"""
|
|
174
|
-
Start streaming data for specified symbols.
|
|
175
|
-
Can be called multiple times to add more symbols.
|
|
176
|
-
|
|
177
|
-
Args:
|
|
178
|
-
symbols (list[tuple[str, models.RecordType]]): List of symbols to
|
|
179
|
-
watch with their respective record types.
|
|
180
|
-
"""
|
|
181
|
-
if not self.data_iterator:
|
|
182
|
-
console.logger.error("Not connected. Call connect() first.")
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
with self._symbols_lock:
|
|
186
|
-
new_symbols = set(symbols) - self._watched_symbols
|
|
187
|
-
already_watched = set(symbols) & self._watched_symbols
|
|
188
|
-
|
|
189
|
-
self._watched_symbols.update(new_symbols)
|
|
190
|
-
|
|
191
|
-
if new_symbols:
|
|
192
|
-
console.logger.info(f"Added new symbols: {new_symbols}")
|
|
193
|
-
if already_watched:
|
|
194
|
-
console.logger.info(f"Already watching: {already_watched}")
|
|
195
|
-
console.logger.info(
|
|
196
|
-
f"Currently watching: {len(self._watched_symbols)} symbols"
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
if self._streaming_thread is None or not self._streaming_thread.is_alive():
|
|
200
|
-
self._streaming_thread = threading.Thread(
|
|
201
|
-
target=self._stream, name="CSVDatafeedStreaming", daemon=True
|
|
202
|
-
)
|
|
203
|
-
self._streaming_thread.start()
|
|
204
|
-
console.logger.info("Started CSV streaming thread")
|
|
205
|
-
|
|
206
|
-
def _stream(self):
|
|
207
|
-
"""Internal method that runs in background thread to stream CSV data."""
|
|
208
|
-
console.logger.info("CSV streaming thread started")
|
|
209
|
-
|
|
210
|
-
should_delay = self._streaming_delay > 0
|
|
211
|
-
delay_time = self._streaming_delay
|
|
212
|
-
|
|
213
|
-
while not self._stop_event.is_set():
|
|
214
|
-
try:
|
|
215
|
-
chunk = next(self.data_iterator)
|
|
216
|
-
row = chunk.iloc[0]
|
|
217
|
-
|
|
218
|
-
symbol = row["symbol"]
|
|
219
|
-
rtype = row["rtype"]
|
|
220
|
-
|
|
221
|
-
with self._symbols_lock:
|
|
222
|
-
symbol_key = (symbol, models.RecordType(rtype))
|
|
223
|
-
if symbol_key not in self._watched_symbols:
|
|
224
|
-
continue
|
|
225
|
-
|
|
226
|
-
bar_event = events.Market.IncomingBar(
|
|
227
|
-
ts_event=pd.Timestamp(row["ts_event"], unit="ns", tz="UTC"),
|
|
228
|
-
symbol=symbol,
|
|
229
|
-
bar=models.Bar(
|
|
230
|
-
open=row["open"] / 1e9,
|
|
231
|
-
high=row["high"] / 1e9,
|
|
232
|
-
low=row["low"] / 1e9,
|
|
233
|
-
close=row["close"] / 1e9,
|
|
234
|
-
volume=int(row["volume"]),
|
|
235
|
-
),
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
self.event_bus.publish(bar_event)
|
|
239
|
-
|
|
240
|
-
if should_delay:
|
|
241
|
-
time.sleep(delay_time)
|
|
242
|
-
|
|
243
|
-
except StopIteration:
|
|
244
|
-
console.logger.info("CSV datafeed reached end of file")
|
|
245
|
-
break
|
|
246
|
-
except ValueError as e:
|
|
247
|
-
console.logger.warning(f"Invalid rtype {row['rtype']} in CSV data: {e}")
|
|
248
|
-
continue
|
|
249
|
-
except Exception as e:
|
|
250
|
-
console.logger.error(f"CSV datafeed error reading data: {e}")
|
|
251
|
-
break
|
|
252
|
-
|
|
253
|
-
console.logger.info("CSV streaming thread stopped")
|
|
254
|
-
|
|
255
|
-
def unwatch(self, symbols):
|
|
256
|
-
"""
|
|
257
|
-
Stop watching specific symbols.
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
symbols (list[tuple[str, models.RecordType]]): List of symbols to
|
|
261
|
-
stop watching.
|
|
262
|
-
"""
|
|
263
|
-
with self._symbols_lock:
|
|
264
|
-
for symbol in symbols:
|
|
265
|
-
self._watched_symbols.discard(symbol)
|
|
266
|
-
|
|
267
|
-
console.logger.info(f"Stopped watching symbols: {symbols}")
|
|
268
|
-
console.logger.info(f"Still watching: {self._watched_symbols}")
|
|
269
|
-
|
|
270
|
-
def disconnect(self):
|
|
271
|
-
"""
|
|
272
|
-
Disconnect from CSV datafeed.
|
|
273
|
-
"""
|
|
274
|
-
self._stop_event.set()
|
|
275
|
-
|
|
276
|
-
if self._streaming_thread and self._streaming_thread.is_alive():
|
|
277
|
-
console.logger.info("Waiting for streaming thread to stop...")
|
|
278
|
-
self._streaming_thread.join(timeout=5.0)
|
|
279
|
-
if self._streaming_thread.is_alive():
|
|
280
|
-
console.logger.warning("Streaming thread did not stop within timeout")
|
|
281
|
-
|
|
282
|
-
with self._symbols_lock:
|
|
283
|
-
self._watched_symbols.clear()
|
|
284
|
-
|
|
285
|
-
if self.data_iterator is not None:
|
|
286
|
-
try:
|
|
287
|
-
self.data_iterator.close()
|
|
288
|
-
console.logger.info("CSV iterator closed successfully")
|
|
289
|
-
except Exception as e:
|
|
290
|
-
console.logger.warning(f"Error closing CSV iterator: {e}")
|
|
291
|
-
finally:
|
|
292
|
-
self.data_iterator = None
|
|
293
|
-
|
|
294
|
-
self.csv_path = None
|
|
295
|
-
self._streaming_thread = None
|
|
296
|
-
|
|
297
|
-
CSVDatafeed._instance = None
|