onesecondtrader 0.33.0__tar.gz → 0.35.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.33.0 → onesecondtrader-0.35.0}/PKG-INFO +1 -1
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/pyproject.toml +1 -1
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/__init__.py +4 -1
- onesecondtrader-0.35.0/src/onesecondtrader/datafeeds/__init__.py +7 -0
- onesecondtrader-0.35.0/src/onesecondtrader/datafeeds/base.py +19 -0
- onesecondtrader-0.35.0/src/onesecondtrader/datafeeds/simulated.py +99 -0
- onesecondtrader-0.35.0/src/onesecondtrader/observers/__init__.py +5 -0
- onesecondtrader-0.35.0/src/onesecondtrader/observers/csvbookkeeper.py +180 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/LICENSE +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/README.md +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/brokers/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/brokers/base.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/brokers/simulated.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/events/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/events/bases.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/events/market.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/events/requests.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/events/responses.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/averages.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/bar.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/base.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/messaging/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/messaging/eventbus.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/messaging/subscriber.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/models/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/models/data.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/models/orders.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/models/records.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/strategies/__init__.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/strategies/base.py +0 -0
- {onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/strategies/sma_crossover.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.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.35.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"}
|
|
@@ -4,6 +4,7 @@ __all__ = [
|
|
|
4
4
|
"BarReceived",
|
|
5
5
|
"BrokerBase",
|
|
6
6
|
"Close",
|
|
7
|
+
"Datafeed",
|
|
7
8
|
"FillRecord",
|
|
8
9
|
"High",
|
|
9
10
|
"Indicator",
|
|
@@ -15,14 +16,16 @@ __all__ = [
|
|
|
15
16
|
"OrderSide",
|
|
16
17
|
"OrderSubmission",
|
|
17
18
|
"OrderType",
|
|
18
|
-
"SimpleMovingAverage",
|
|
19
19
|
"SimulatedBroker",
|
|
20
|
+
"SimulatedDatafeed",
|
|
21
|
+
"SimpleMovingAverage",
|
|
20
22
|
"SMACrossover",
|
|
21
23
|
"StrategyBase",
|
|
22
24
|
"Volume",
|
|
23
25
|
]
|
|
24
26
|
|
|
25
27
|
from onesecondtrader.brokers import BrokerBase, SimulatedBroker
|
|
28
|
+
from onesecondtrader.datafeeds import Datafeed, SimulatedDatafeed
|
|
26
29
|
from onesecondtrader.events import (
|
|
27
30
|
BarProcessed,
|
|
28
31
|
BarReceived,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
from onesecondtrader import events, messaging, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Datafeed(abc.ABC):
|
|
7
|
+
def __init__(self, event_bus: messaging.EventBus) -> None:
|
|
8
|
+
self._event_bus = event_bus
|
|
9
|
+
|
|
10
|
+
def _publish(self, event: events.EventBase) -> None:
|
|
11
|
+
self._event_bus.publish(event)
|
|
12
|
+
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
def stream(self, symbols: list[str], bar_period: models.BarPeriod) -> None:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
def shutdown(self) -> None:
|
|
19
|
+
pass
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from onesecondtrader import events, messaging, models
|
|
7
|
+
from .base import Datafeed
|
|
8
|
+
|
|
9
|
+
_RTYPE_MAP = {
|
|
10
|
+
models.BarPeriod.SECOND: 32,
|
|
11
|
+
models.BarPeriod.MINUTE: 33,
|
|
12
|
+
models.BarPeriod.HOUR: 34,
|
|
13
|
+
models.BarPeriod.DAY: 35,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SimulatedDatafeed(Datafeed):
|
|
18
|
+
csv_path: str = ""
|
|
19
|
+
|
|
20
|
+
def __init__(self, event_bus: messaging.EventBus) -> None:
|
|
21
|
+
super().__init__(event_bus)
|
|
22
|
+
self._thread: threading.Thread | None = None
|
|
23
|
+
self._stop_event = threading.Event()
|
|
24
|
+
|
|
25
|
+
def stream(self, symbols: list[str], bar_period: models.BarPeriod) -> None:
|
|
26
|
+
csv_path = pathlib.Path(self.csv_path)
|
|
27
|
+
if self._thread and self._thread.is_alive():
|
|
28
|
+
raise RuntimeError("Already streaming")
|
|
29
|
+
if not csv_path.exists():
|
|
30
|
+
raise FileNotFoundError(f"CSV file not found: {csv_path}")
|
|
31
|
+
if not symbols:
|
|
32
|
+
raise ValueError("symbols list cannot be empty")
|
|
33
|
+
|
|
34
|
+
self._stop_event.clear()
|
|
35
|
+
self._thread = threading.Thread(
|
|
36
|
+
target=self._stream,
|
|
37
|
+
args=(symbols, bar_period),
|
|
38
|
+
name=self.__class__.__name__,
|
|
39
|
+
daemon=False,
|
|
40
|
+
)
|
|
41
|
+
self._thread.start()
|
|
42
|
+
|
|
43
|
+
def wait(self) -> None:
|
|
44
|
+
if self._thread:
|
|
45
|
+
self._thread.join()
|
|
46
|
+
|
|
47
|
+
def shutdown(self) -> None:
|
|
48
|
+
self._stop_event.set()
|
|
49
|
+
if self._thread and self._thread.is_alive():
|
|
50
|
+
self._thread.join()
|
|
51
|
+
|
|
52
|
+
def _stream(self, symbols: list[str], bar_period: models.BarPeriod) -> None:
|
|
53
|
+
symbols_set = set(symbols)
|
|
54
|
+
rtype = _RTYPE_MAP[bar_period]
|
|
55
|
+
|
|
56
|
+
for chunk in pd.read_csv(
|
|
57
|
+
self.csv_path,
|
|
58
|
+
usecols=[
|
|
59
|
+
"ts_event",
|
|
60
|
+
"rtype",
|
|
61
|
+
"open",
|
|
62
|
+
"high",
|
|
63
|
+
"low",
|
|
64
|
+
"close",
|
|
65
|
+
"volume",
|
|
66
|
+
"symbol",
|
|
67
|
+
],
|
|
68
|
+
dtype={
|
|
69
|
+
"ts_event": int,
|
|
70
|
+
"rtype": int,
|
|
71
|
+
"open": int,
|
|
72
|
+
"high": int,
|
|
73
|
+
"low": int,
|
|
74
|
+
"close": int,
|
|
75
|
+
"volume": int,
|
|
76
|
+
"symbol": str,
|
|
77
|
+
},
|
|
78
|
+
chunksize=10_000,
|
|
79
|
+
):
|
|
80
|
+
for row in chunk.itertuples():
|
|
81
|
+
if self._stop_event.is_set():
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if row.symbol not in symbols_set or row.rtype != rtype:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
self._publish(
|
|
88
|
+
events.BarReceived(
|
|
89
|
+
ts_event=pd.Timestamp(row.ts_event, unit="ns", tz="UTC"),
|
|
90
|
+
symbol=row.symbol,
|
|
91
|
+
bar_period=bar_period,
|
|
92
|
+
open=row.open / 1e9,
|
|
93
|
+
high=row.high / 1e9,
|
|
94
|
+
low=row.low / 1e9,
|
|
95
|
+
close=row.close / 1e9,
|
|
96
|
+
volume=row.volume,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
self._event_bus.wait_until_system_idle()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from onesecondtrader import events, messaging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CSVBookkeeper(messaging.Subscriber):
|
|
9
|
+
BATCH_SIZE: int = 1000
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self, event_bus: messaging.EventBus, results_path: pathlib.Path
|
|
13
|
+
) -> None:
|
|
14
|
+
self._results_path = results_path
|
|
15
|
+
self._bars_buffer: list[dict] = []
|
|
16
|
+
self._fills_buffer: list[dict] = []
|
|
17
|
+
self._orders_buffer: list[dict] = []
|
|
18
|
+
|
|
19
|
+
super().__init__(event_bus)
|
|
20
|
+
self._subscribe(
|
|
21
|
+
events.BarProcessed,
|
|
22
|
+
events.OrderFilled,
|
|
23
|
+
events.OrderSubmission,
|
|
24
|
+
events.OrderModification,
|
|
25
|
+
events.OrderCancellation,
|
|
26
|
+
events.OrderSubmissionAccepted,
|
|
27
|
+
events.OrderModificationAccepted,
|
|
28
|
+
events.OrderCancellationAccepted,
|
|
29
|
+
events.OrderSubmissionRejected,
|
|
30
|
+
events.OrderModificationRejected,
|
|
31
|
+
events.OrderCancellationRejected,
|
|
32
|
+
events.OrderExpired,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def _on_event(self, event: events.EventBase) -> None:
|
|
36
|
+
match event:
|
|
37
|
+
case events.BarProcessed() as e:
|
|
38
|
+
self._on_processed_bar(e)
|
|
39
|
+
case events.OrderFilled() as e:
|
|
40
|
+
self._on_fill(e)
|
|
41
|
+
case events.OrderSubmission() as e:
|
|
42
|
+
self._on_order_event(e, "submission_requested")
|
|
43
|
+
case events.OrderModification() as e:
|
|
44
|
+
self._on_order_event(e, "modification_requested")
|
|
45
|
+
case events.OrderCancellation() as e:
|
|
46
|
+
self._on_order_event(e, "cancellation_requested")
|
|
47
|
+
case events.OrderSubmissionAccepted() as e:
|
|
48
|
+
self._on_order_event(e, "submission_accepted")
|
|
49
|
+
case events.OrderModificationAccepted() as e:
|
|
50
|
+
self._on_order_event(e, "modification_accepted")
|
|
51
|
+
case events.OrderCancellationAccepted() as e:
|
|
52
|
+
self._on_order_event(e, "cancellation_accepted")
|
|
53
|
+
case events.OrderSubmissionRejected() as e:
|
|
54
|
+
self._on_order_event(e, "submission_rejected")
|
|
55
|
+
case events.OrderModificationRejected() as e:
|
|
56
|
+
self._on_order_event(e, "modification_rejected")
|
|
57
|
+
case events.OrderCancellationRejected() as e:
|
|
58
|
+
self._on_order_event(e, "cancellation_rejected")
|
|
59
|
+
case events.OrderExpired() as e:
|
|
60
|
+
self._on_order_event(e, "expired")
|
|
61
|
+
|
|
62
|
+
def _on_processed_bar(self, event: events.BarProcessed) -> None:
|
|
63
|
+
record = {
|
|
64
|
+
"ts_event": event.ts_event,
|
|
65
|
+
"symbol": event.symbol,
|
|
66
|
+
"bar_period": event.bar_period.name,
|
|
67
|
+
"open": event.open,
|
|
68
|
+
"high": event.high,
|
|
69
|
+
"low": event.low,
|
|
70
|
+
"close": event.close,
|
|
71
|
+
"volume": event.volume,
|
|
72
|
+
}
|
|
73
|
+
record.update(event.indicators)
|
|
74
|
+
self._bars_buffer.append(record)
|
|
75
|
+
if len(self._bars_buffer) >= self.BATCH_SIZE:
|
|
76
|
+
self._flush_bars()
|
|
77
|
+
|
|
78
|
+
def _on_fill(self, event: events.OrderFilled) -> None:
|
|
79
|
+
self._fills_buffer.append(
|
|
80
|
+
{
|
|
81
|
+
"ts_event": event.ts_event,
|
|
82
|
+
"fill_id": str(event.fill_id),
|
|
83
|
+
"broker_fill_id": event.broker_fill_id,
|
|
84
|
+
"order_id": str(event.associated_order_id),
|
|
85
|
+
"symbol": event.symbol,
|
|
86
|
+
"side": event.side.name,
|
|
87
|
+
"quantity": event.quantity_filled,
|
|
88
|
+
"price": event.fill_price,
|
|
89
|
+
"commission": event.commission,
|
|
90
|
+
"exchange": event.exchange,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
if len(self._fills_buffer) >= self.BATCH_SIZE:
|
|
94
|
+
self._flush_fills()
|
|
95
|
+
|
|
96
|
+
def _on_order_event(self, event: events.EventBase, event_type: str) -> None:
|
|
97
|
+
record: dict = {
|
|
98
|
+
"ts_event": event.ts_event,
|
|
99
|
+
"event_type": event_type,
|
|
100
|
+
}
|
|
101
|
+
match event:
|
|
102
|
+
case events.OrderSubmission():
|
|
103
|
+
record.update(
|
|
104
|
+
{
|
|
105
|
+
"order_id": str(event.system_order_id),
|
|
106
|
+
"symbol": event.symbol,
|
|
107
|
+
"order_type": event.order_type.name,
|
|
108
|
+
"side": event.side.name,
|
|
109
|
+
"quantity": event.quantity,
|
|
110
|
+
"limit_price": event.limit_price,
|
|
111
|
+
"stop_price": event.stop_price,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
case events.OrderModification():
|
|
115
|
+
record.update(
|
|
116
|
+
{
|
|
117
|
+
"order_id": str(event.system_order_id),
|
|
118
|
+
"symbol": event.symbol,
|
|
119
|
+
"quantity": event.quantity,
|
|
120
|
+
"limit_price": event.limit_price,
|
|
121
|
+
"stop_price": event.stop_price,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
case events.OrderCancellation():
|
|
125
|
+
record.update(
|
|
126
|
+
{
|
|
127
|
+
"order_id": str(event.system_order_id),
|
|
128
|
+
"symbol": event.symbol,
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
case events.OrderSubmissionAccepted() | events.OrderModificationAccepted():
|
|
132
|
+
record.update(
|
|
133
|
+
{
|
|
134
|
+
"order_id": str(event.associated_order_id),
|
|
135
|
+
"broker_order_id": event.broker_order_id,
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
case events.OrderCancellationAccepted():
|
|
139
|
+
record.update({"order_id": str(event.associated_order_id)})
|
|
140
|
+
case (
|
|
141
|
+
events.OrderSubmissionRejected()
|
|
142
|
+
| events.OrderModificationRejected()
|
|
143
|
+
| events.OrderCancellationRejected()
|
|
144
|
+
):
|
|
145
|
+
record.update({"order_id": str(event.associated_order_id)})
|
|
146
|
+
case events.OrderExpired():
|
|
147
|
+
record.update({"order_id": str(event.associated_order_id)})
|
|
148
|
+
|
|
149
|
+
self._orders_buffer.append(record)
|
|
150
|
+
if len(self._orders_buffer) >= self.BATCH_SIZE:
|
|
151
|
+
self._flush_orders()
|
|
152
|
+
|
|
153
|
+
def _flush_bars(self) -> None:
|
|
154
|
+
if not self._bars_buffer:
|
|
155
|
+
return
|
|
156
|
+
df = pd.DataFrame(self._bars_buffer)
|
|
157
|
+
path = self._results_path / "processed_bars.csv"
|
|
158
|
+
df.to_csv(path, mode="a", header=not path.exists(), index=False)
|
|
159
|
+
self._bars_buffer.clear()
|
|
160
|
+
|
|
161
|
+
def _flush_fills(self) -> None:
|
|
162
|
+
if not self._fills_buffer:
|
|
163
|
+
return
|
|
164
|
+
df = pd.DataFrame(self._fills_buffer)
|
|
165
|
+
path = self._results_path / "fills.csv"
|
|
166
|
+
df.to_csv(path, mode="a", header=not path.exists(), index=False)
|
|
167
|
+
self._fills_buffer.clear()
|
|
168
|
+
|
|
169
|
+
def _flush_orders(self) -> None:
|
|
170
|
+
if not self._orders_buffer:
|
|
171
|
+
return
|
|
172
|
+
df = pd.DataFrame(self._orders_buffer)
|
|
173
|
+
path = self._results_path / "orders.csv"
|
|
174
|
+
df.to_csv(path, mode="a", header=not path.exists(), index=False)
|
|
175
|
+
self._orders_buffer.clear()
|
|
176
|
+
|
|
177
|
+
def _cleanup(self) -> None:
|
|
178
|
+
self._flush_bars()
|
|
179
|
+
self._flush_fills()
|
|
180
|
+
self._flush_orders()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/__init__.py
RENAMED
|
File without changes
|
{onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/indicators/averages.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/messaging/subscriber.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/strategies/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{onesecondtrader-0.33.0 → onesecondtrader-0.35.0}/src/onesecondtrader/strategies/sma_crossover.py
RENAMED
|
File without changes
|