onesecondtrader 0.40.0__py3-none-any.whl → 0.41.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.
@@ -18,6 +18,7 @@ __all__ = [
18
18
  "OrderSide",
19
19
  "OrderSubmission",
20
20
  "OrderType",
21
+ "ParamSpec",
21
22
  "SimulatedBroker",
22
23
  "SimulatedDatafeed",
23
24
  "SimpleMovingAverage",
@@ -52,5 +53,6 @@ from onesecondtrader.core.models import (
52
53
  OrderRecord,
53
54
  OrderSide,
54
55
  OrderType,
56
+ ParamSpec,
55
57
  )
56
58
  from onesecondtrader.core.strategies import SMACrossover, StrategyBase
@@ -21,6 +21,8 @@ _PRICE_SCALE = 1e9
21
21
 
22
22
  class SimulatedDatafeed(DatafeedBase):
23
23
  db_path: str = ""
24
+ start_ts: int | None = None
25
+ end_ts: int | None = None
24
26
 
25
27
  def __init__(self, event_bus: messaging.EventBus) -> None:
26
28
  super().__init__(event_bus)
@@ -51,9 +53,14 @@ class SimulatedDatafeed(DatafeedBase):
51
53
  self._connected = False
52
54
 
53
55
  def subscribe(self, symbol: str, bar_period: models.BarPeriod) -> None:
54
- if (symbol, bar_period) in self._subscriptions:
55
- return
56
56
  self._subscriptions.add((symbol, bar_period))
57
+
58
+ def unsubscribe(self, symbol: str, bar_period: models.BarPeriod) -> None:
59
+ self._subscriptions.discard((symbol, bar_period))
60
+
61
+ def wait_until_complete(self) -> None:
62
+ if not self._subscriptions:
63
+ return
57
64
  if self._thread is None or not self._thread.is_alive():
58
65
  self._stop_event.clear()
59
66
  self._thread = threading.Thread(
@@ -62,9 +69,7 @@ class SimulatedDatafeed(DatafeedBase):
62
69
  daemon=False,
63
70
  )
64
71
  self._thread.start()
65
-
66
- def unsubscribe(self, symbol: str, bar_period: models.BarPeriod) -> None:
67
- self._subscriptions.discard((symbol, bar_period))
72
+ self._thread.join()
68
73
 
69
74
  def _stream(self) -> None:
70
75
  if not self._connection:
@@ -94,15 +99,25 @@ class SimulatedDatafeed(DatafeedBase):
94
99
  placeholders_ids = ",".join("?" * len(id_list))
95
100
  placeholders_rtypes = ",".join("?" * len(rtype_list))
96
101
 
102
+ date_filter = ""
103
+ params = id_list + rtype_list
104
+ if self.start_ts is not None:
105
+ date_filter += " AND ts_event >= ?"
106
+ params.append(self.start_ts)
107
+ if self.end_ts is not None:
108
+ date_filter += " AND ts_event <= ?"
109
+ params.append(self.end_ts)
110
+
97
111
  query = f"""
98
112
  SELECT instrument_id, rtype, ts_event, open, high, low, close, volume
99
113
  FROM ohlcv
100
114
  WHERE instrument_id IN ({placeholders_ids})
101
115
  AND rtype IN ({placeholders_rtypes})
116
+ {date_filter}
102
117
  ORDER BY ts_event
103
118
  """
104
119
 
105
- cursor.execute(query, id_list + rtype_list)
120
+ cursor.execute(query, params)
106
121
 
107
122
  while True:
108
123
  if self._stop_event.is_set():
@@ -27,3 +27,6 @@ class DatafeedBase(abc.ABC):
27
27
  @abc.abstractmethod
28
28
  def unsubscribe(self, symbol: str, bar_period: models.BarPeriod) -> None:
29
29
  pass
30
+
31
+ def wait_until_complete(self) -> None:
32
+ pass
@@ -5,8 +5,10 @@ __all__ = [
5
5
  "OrderType",
6
6
  "OrderRecord",
7
7
  "FillRecord",
8
+ "ParamSpec",
8
9
  ]
9
10
 
10
11
  from .data import BarPeriod, InputSource
11
12
  from .orders import OrderSide, OrderType
12
13
  from .records import OrderRecord, FillRecord
14
+ from .params import ParamSpec
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import enum
5
+
6
+
7
+ @dataclasses.dataclass
8
+ class ParamSpec:
9
+ default: int | float | str | bool | enum.Enum
10
+ min: int | float | None = None
11
+ max: int | float | None = None
12
+ step: int | float | None = None
13
+ choices: list | None = None
14
+
15
+ @property
16
+ def resolved_choices(self) -> list | None:
17
+ if self.choices is not None:
18
+ return self.choices
19
+ if isinstance(self.default, enum.Enum):
20
+ return list(type(self.default))
21
+ return None
@@ -10,11 +10,17 @@ from onesecondtrader.core import events, indicators, messaging, models
10
10
 
11
11
 
12
12
  class StrategyBase(messaging.Subscriber, abc.ABC):
13
+ name: str = ""
13
14
  symbols: list[str] = []
14
- bar_period: models.BarPeriod = models.BarPeriod.SECOND
15
+ parameters: dict[str, models.ParamSpec] = {}
15
16
 
16
- def __init__(self, event_bus: messaging.EventBus) -> None:
17
+ def __init__(self, event_bus: messaging.EventBus, **overrides) -> None:
17
18
  super().__init__(event_bus)
19
+
20
+ for name, spec in self.parameters.items():
21
+ value = overrides.get(name, spec.default)
22
+ setattr(self, name, value)
23
+
18
24
  self._subscribe(
19
25
  events.BarReceived,
20
26
  events.OrderSubmissionAccepted,
@@ -179,7 +185,7 @@ class StrategyBase(messaging.Subscriber, abc.ABC):
179
185
  def _on_bar_received(self, event: events.BarReceived) -> None:
180
186
  if event.symbol not in self.symbols:
181
187
  return
182
- if event.bar_period != self.bar_period:
188
+ if event.bar_period != self.bar_period: # type: ignore[attr-defined]
183
189
  return
184
190
 
185
191
  self._current_symbol = event.symbol
@@ -3,16 +3,20 @@ from .base import StrategyBase
3
3
 
4
4
 
5
5
  class SMACrossover(StrategyBase):
6
- fast_period: int = 20
7
- slow_period: int = 100
8
- quantity: float = 1.0
6
+ name = "SMA Crossover"
7
+ parameters = {
8
+ "bar_period": models.ParamSpec(default=models.BarPeriod.SECOND),
9
+ "fast_period": models.ParamSpec(default=20, min=5, max=100, step=1),
10
+ "slow_period": models.ParamSpec(default=100, min=10, max=500, step=1),
11
+ "quantity": models.ParamSpec(default=1.0, min=0.1, max=100.0, step=0.1),
12
+ }
9
13
 
10
14
  def setup(self) -> None:
11
15
  self.fast_sma = self.add_indicator(
12
- indicators.SimpleMovingAverage(period=self.fast_period)
16
+ indicators.SimpleMovingAverage(period=self.fast_period) # type: ignore[attr-defined]
13
17
  )
14
18
  self.slow_sma = self.add_indicator(
15
- indicators.SimpleMovingAverage(period=self.slow_period)
19
+ indicators.SimpleMovingAverage(period=self.slow_period) # type: ignore[attr-defined]
16
20
  )
17
21
 
18
22
  def on_bar(self, event: events.BarReceived) -> None:
@@ -22,7 +26,9 @@ class SMACrossover(StrategyBase):
22
26
  and self.position <= 0
23
27
  ):
24
28
  self.submit_order(
25
- models.OrderType.MARKET, models.OrderSide.BUY, self.quantity
29
+ models.OrderType.MARKET,
30
+ models.OrderSide.BUY,
31
+ self.quantity, # type: ignore[attr-defined]
26
32
  )
27
33
 
28
34
  if (
@@ -31,5 +37,7 @@ class SMACrossover(StrategyBase):
31
37
  and self.position >= 0
32
38
  ):
33
39
  self.submit_order(
34
- models.OrderType.MARKET, models.OrderSide.SELL, self.quantity
40
+ models.OrderType.MARKET,
41
+ models.OrderSide.SELL,
42
+ self.quantity, # type: ignore[attr-defined]
35
43
  )
@@ -0,0 +1,3 @@
1
+ __all__ = ["app"]
2
+
3
+ from .app import app