onesecondtrader 0.16.0__tar.gz → 0.18.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.16.0 → onesecondtrader-0.18.0}/PKG-INFO +1 -1
- {onesecondtrader-0.16.0 → onesecondtrader-0.18.0}/pyproject.toml +1 -1
- onesecondtrader-0.18.0/src/onesecondtrader/core.py +147 -0
- {onesecondtrader-0.16.0 → onesecondtrader-0.18.0}/src/onesecondtrader/indicators.py +16 -12
- onesecondtrader-0.16.0/src/onesecondtrader/ontology.py +0 -18
- {onesecondtrader-0.16.0 → onesecondtrader-0.18.0}/LICENSE +0 -0
- {onesecondtrader-0.16.0 → onesecondtrader-0.18.0}/README.md +0 -0
- {onesecondtrader-0.16.0 → onesecondtrader-0.18.0}/src/onesecondtrader/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onesecondtrader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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.18.0"
|
|
4
4
|
description = "The Trading Infrastructure Toolkit for Python. Research, simulate, and deploy algorithmic trading strategies — all in one place."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Nils P. Kujath",email = "63961429+NilsKujath@users.noreply.github.com"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module containing the backbone of OneSecondTrader's event-driven architecture.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import dataclasses
|
|
7
|
+
import enum
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import queue
|
|
10
|
+
import threading
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Models:
|
|
17
|
+
"""
|
|
18
|
+
Namespace for all models.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class RecordType(enum.Enum):
|
|
22
|
+
OHLCV_1S = 32
|
|
23
|
+
OHLCV_1M = 33
|
|
24
|
+
OHLCV_1H = 34
|
|
25
|
+
OHLCV_1D = 35
|
|
26
|
+
|
|
27
|
+
class OrderSide(enum.Enum):
|
|
28
|
+
BUY = enum.auto()
|
|
29
|
+
SELL = enum.auto()
|
|
30
|
+
|
|
31
|
+
class OrderType(enum.Enum):
|
|
32
|
+
MARKET = enum.auto()
|
|
33
|
+
LIMIT = enum.auto()
|
|
34
|
+
STOP = enum.auto()
|
|
35
|
+
STOP_LIMIT = enum.auto()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Events:
|
|
39
|
+
"""
|
|
40
|
+
Namespace for all events.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
44
|
+
class BaseEvent:
|
|
45
|
+
ts_event: pd.Timestamp = dataclasses.field(
|
|
46
|
+
default_factory=lambda: pd.Timestamp.now(tz="UTC")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# SYSTEM EVENTS
|
|
50
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
51
|
+
class SystemShutdown(BaseEvent):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
# MARKET EVENTS
|
|
55
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
56
|
+
class IncomingBar(BaseEvent):
|
|
57
|
+
ts_event: pd.Timestamp
|
|
58
|
+
symbol: str
|
|
59
|
+
record_type: Models.RecordType
|
|
60
|
+
open: float
|
|
61
|
+
high: float
|
|
62
|
+
low: float
|
|
63
|
+
close: float
|
|
64
|
+
volume: int | None = None
|
|
65
|
+
|
|
66
|
+
# BROKER REQUEST EVENTS
|
|
67
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
68
|
+
class Order(BaseEvent):
|
|
69
|
+
order_id: uuid.UUID = dataclasses.field(default_factory=lambda: uuid.uuid4())
|
|
70
|
+
symbol: str
|
|
71
|
+
order_type: Models.OrderType
|
|
72
|
+
side: Models.OrderSide
|
|
73
|
+
quantity: float
|
|
74
|
+
limit_price: float | None = None
|
|
75
|
+
stop_price: float | None = None
|
|
76
|
+
|
|
77
|
+
# BROKER RESPONSE EVENTS
|
|
78
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
79
|
+
class Fill(BaseEvent):
|
|
80
|
+
fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
|
|
81
|
+
broker_fill_id: str | None = None
|
|
82
|
+
associated_order_id: uuid.UUID
|
|
83
|
+
side: Models.OrderSide
|
|
84
|
+
quantity_filled: float
|
|
85
|
+
fill_price: float
|
|
86
|
+
commission: float
|
|
87
|
+
exchange: str = "SIMULATED"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class BaseConsumer(abc.ABC):
|
|
91
|
+
"""
|
|
92
|
+
Base class for all consumers.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self) -> None:
|
|
96
|
+
self._queue: queue.Queue[Events.BaseEvent] = queue.Queue()
|
|
97
|
+
self._thread = threading.Thread(target=self._consume, daemon=True)
|
|
98
|
+
self._thread.start()
|
|
99
|
+
|
|
100
|
+
@abc.abstractmethod
|
|
101
|
+
def on_event(self, event: Events.BaseEvent) -> None:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
def receive(self, event: Events.BaseEvent) -> None:
|
|
105
|
+
self._queue.put(event)
|
|
106
|
+
|
|
107
|
+
def _consume(self) -> None:
|
|
108
|
+
while True:
|
|
109
|
+
event = self._queue.get()
|
|
110
|
+
if isinstance(event, Events.SystemShutdown):
|
|
111
|
+
break
|
|
112
|
+
self.on_event(event)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class EventBus:
|
|
116
|
+
"""
|
|
117
|
+
Event bus for publishing events to the consumers subscribed to them.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self) -> None:
|
|
121
|
+
self._subscriptions: defaultdict[type[Events.BaseEvent], list[BaseConsumer]] = (
|
|
122
|
+
defaultdict(list)
|
|
123
|
+
)
|
|
124
|
+
self._lock: threading.Lock = threading.Lock()
|
|
125
|
+
|
|
126
|
+
def subscribe(self, subscriber: BaseConsumer, event_type: type[Events.BaseEvent]):
|
|
127
|
+
with self._lock:
|
|
128
|
+
if subscriber not in self._subscriptions[event_type]:
|
|
129
|
+
self._subscriptions[event_type].append(subscriber)
|
|
130
|
+
|
|
131
|
+
def unsubscribe(self, subscriber: BaseConsumer):
|
|
132
|
+
with self._lock:
|
|
133
|
+
for consumer_list in self._subscriptions.values():
|
|
134
|
+
if subscriber in consumer_list:
|
|
135
|
+
consumer_list.remove(subscriber)
|
|
136
|
+
|
|
137
|
+
def publish(self, event: Events.BaseEvent) -> None:
|
|
138
|
+
with self._lock:
|
|
139
|
+
consumers = list(self._subscriptions[type(event)])
|
|
140
|
+
for consumer in consumers:
|
|
141
|
+
consumer.receive(event)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
event_bus = EventBus()
|
|
145
|
+
"""
|
|
146
|
+
Global event bus instance.
|
|
147
|
+
"""
|
|
@@ -8,7 +8,7 @@ import numpy as np
|
|
|
8
8
|
import threading
|
|
9
9
|
|
|
10
10
|
from collections import deque
|
|
11
|
-
from onesecondtrader.
|
|
11
|
+
from onesecondtrader.core import Events
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class BaseIndicator(abc.ABC):
|
|
@@ -26,13 +26,13 @@ class BaseIndicator(abc.ABC):
|
|
|
26
26
|
def name(self) -> str:
|
|
27
27
|
pass
|
|
28
28
|
|
|
29
|
-
def update(self, incoming_bar:
|
|
29
|
+
def update(self, incoming_bar: Events.IncomingBar) -> None:
|
|
30
30
|
_latest_value: float = self._compute_indicator(incoming_bar)
|
|
31
31
|
with self._lock:
|
|
32
32
|
self._history.append(_latest_value)
|
|
33
33
|
|
|
34
34
|
@abc.abstractmethod
|
|
35
|
-
def _compute_indicator(self, incoming_bar:
|
|
35
|
+
def _compute_indicator(self, incoming_bar: Events.IncomingBar) -> float:
|
|
36
36
|
pass
|
|
37
37
|
|
|
38
38
|
@property
|
|
@@ -79,24 +79,28 @@ class SimpleMovingAverage(BaseIndicator):
|
|
|
79
79
|
def name(self) -> str:
|
|
80
80
|
return f"SMA_{self.period}_{self.input_source.name}"
|
|
81
81
|
|
|
82
|
-
def _compute_indicator(self,
|
|
83
|
-
value: float = self._extract_input(
|
|
82
|
+
def _compute_indicator(self, incoming_bar: Events.IncomingBar) -> float:
|
|
83
|
+
value: float = self._extract_input(incoming_bar)
|
|
84
84
|
self._window.append(value)
|
|
85
85
|
if len(self._window) < self.period:
|
|
86
86
|
return np.nan
|
|
87
87
|
return sum(self._window) / self.period
|
|
88
88
|
|
|
89
|
-
def _extract_input(self,
|
|
89
|
+
def _extract_input(self, incoming_bar: Events.IncomingBar) -> float:
|
|
90
90
|
match self.input_source:
|
|
91
91
|
case InputSource.OPEN:
|
|
92
|
-
return
|
|
92
|
+
return incoming_bar.open
|
|
93
93
|
case InputSource.HIGH:
|
|
94
|
-
return
|
|
94
|
+
return incoming_bar.high
|
|
95
95
|
case InputSource.LOW:
|
|
96
|
-
return
|
|
96
|
+
return incoming_bar.low
|
|
97
97
|
case InputSource.CLOSE:
|
|
98
|
-
return
|
|
98
|
+
return incoming_bar.close
|
|
99
99
|
case InputSource.VOLUME:
|
|
100
|
-
return
|
|
100
|
+
return (
|
|
101
|
+
float(incoming_bar.volume)
|
|
102
|
+
if incoming_bar.volume is not None
|
|
103
|
+
else np.nan
|
|
104
|
+
)
|
|
101
105
|
case _:
|
|
102
|
-
return
|
|
106
|
+
return incoming_bar.close
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|