onesecondtrader 0.41.0__py3-none-any.whl → 0.44.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.
Files changed (55) hide show
  1. onesecondtrader/__init__.py +0 -58
  2. onesecondtrader/models/__init__.py +11 -0
  3. onesecondtrader/models/bar_fields.py +23 -0
  4. onesecondtrader/models/bar_period.py +21 -0
  5. onesecondtrader/models/order_types.py +21 -0
  6. onesecondtrader/models/trade_sides.py +20 -0
  7. {onesecondtrader-0.41.0.dist-info → onesecondtrader-0.44.0.dist-info}/METADATA +2 -2
  8. onesecondtrader-0.44.0.dist-info/RECORD +10 -0
  9. onesecondtrader/connectors/__init__.py +0 -3
  10. onesecondtrader/connectors/brokers/__init__.py +0 -4
  11. onesecondtrader/connectors/brokers/ib.py +0 -418
  12. onesecondtrader/connectors/brokers/simulated.py +0 -349
  13. onesecondtrader/connectors/datafeeds/__init__.py +0 -4
  14. onesecondtrader/connectors/datafeeds/ib.py +0 -286
  15. onesecondtrader/connectors/datafeeds/simulated.py +0 -167
  16. onesecondtrader/connectors/gateways/__init__.py +0 -3
  17. onesecondtrader/connectors/gateways/ib.py +0 -314
  18. onesecondtrader/core/__init__.py +0 -7
  19. onesecondtrader/core/brokers/__init__.py +0 -3
  20. onesecondtrader/core/brokers/base.py +0 -46
  21. onesecondtrader/core/datafeeds/__init__.py +0 -3
  22. onesecondtrader/core/datafeeds/base.py +0 -32
  23. onesecondtrader/core/events/__init__.py +0 -33
  24. onesecondtrader/core/events/bases.py +0 -29
  25. onesecondtrader/core/events/market.py +0 -22
  26. onesecondtrader/core/events/requests.py +0 -31
  27. onesecondtrader/core/events/responses.py +0 -54
  28. onesecondtrader/core/indicators/__init__.py +0 -13
  29. onesecondtrader/core/indicators/averages.py +0 -56
  30. onesecondtrader/core/indicators/bar.py +0 -47
  31. onesecondtrader/core/indicators/base.py +0 -60
  32. onesecondtrader/core/messaging/__init__.py +0 -7
  33. onesecondtrader/core/messaging/eventbus.py +0 -47
  34. onesecondtrader/core/messaging/subscriber.py +0 -69
  35. onesecondtrader/core/models/__init__.py +0 -14
  36. onesecondtrader/core/models/data.py +0 -18
  37. onesecondtrader/core/models/orders.py +0 -15
  38. onesecondtrader/core/models/params.py +0 -21
  39. onesecondtrader/core/models/records.py +0 -32
  40. onesecondtrader/core/strategies/__init__.py +0 -7
  41. onesecondtrader/core/strategies/base.py +0 -324
  42. onesecondtrader/core/strategies/examples.py +0 -43
  43. onesecondtrader/dashboard/__init__.py +0 -3
  44. onesecondtrader/dashboard/app.py +0 -1677
  45. onesecondtrader/dashboard/registry.py +0 -100
  46. onesecondtrader/orchestrator/__init__.py +0 -7
  47. onesecondtrader/orchestrator/orchestrator.py +0 -105
  48. onesecondtrader/orchestrator/recorder.py +0 -196
  49. onesecondtrader/orchestrator/schema.sql +0 -208
  50. onesecondtrader/secmaster/__init__.py +0 -6
  51. onesecondtrader/secmaster/schema.sql +0 -740
  52. onesecondtrader/secmaster/utils.py +0 -737
  53. onesecondtrader-0.41.0.dist-info/RECORD +0 -49
  54. {onesecondtrader-0.41.0.dist-info → onesecondtrader-0.44.0.dist-info}/WHEEL +0 -0
  55. {onesecondtrader-0.41.0.dist-info → onesecondtrader-0.44.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,324 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import abc
4
- import uuid
5
- from types import SimpleNamespace
6
-
7
- import pandas as pd
8
-
9
- from onesecondtrader.core import events, indicators, messaging, models
10
-
11
-
12
- class StrategyBase(messaging.Subscriber, abc.ABC):
13
- name: str = ""
14
- symbols: list[str] = []
15
- parameters: dict[str, models.ParamSpec] = {}
16
-
17
- def __init__(self, event_bus: messaging.EventBus, **overrides) -> None:
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
-
24
- self._subscribe(
25
- events.BarReceived,
26
- events.OrderSubmissionAccepted,
27
- events.OrderModificationAccepted,
28
- events.OrderCancellationAccepted,
29
- events.OrderSubmissionRejected,
30
- events.OrderModificationRejected,
31
- events.OrderCancellationRejected,
32
- events.OrderFilled,
33
- events.OrderExpired,
34
- )
35
-
36
- self._current_symbol: str = ""
37
- self._current_ts: pd.Timestamp = pd.Timestamp.now(tz="UTC")
38
- self._indicators: list[indicators.Indicator] = []
39
-
40
- self._fills: dict[str, list[models.FillRecord]] = {}
41
- self._positions: dict[str, float] = {}
42
- self._avg_prices: dict[str, float] = {}
43
- self._pending_orders: dict[uuid.UUID, models.OrderRecord] = {}
44
- self._submitted_orders: dict[uuid.UUID, models.OrderRecord] = {}
45
- self._submitted_modifications: dict[uuid.UUID, models.OrderRecord] = {}
46
- self._submitted_cancellations: dict[uuid.UUID, models.OrderRecord] = {}
47
-
48
- # OHLCV as indicators for history access: self.bar.close.history
49
- self.bar = SimpleNamespace(
50
- open=self.add_indicator(indicators.Open()),
51
- high=self.add_indicator(indicators.High()),
52
- low=self.add_indicator(indicators.Low()),
53
- close=self.add_indicator(indicators.Close()),
54
- volume=self.add_indicator(indicators.Volume()),
55
- )
56
-
57
- # Hook for subclasses to register indicators without overriding __init__
58
- self.setup()
59
-
60
- def add_indicator(self, ind: indicators.Indicator) -> indicators.Indicator:
61
- self._indicators.append(ind)
62
- return ind
63
-
64
- @property
65
- def position(self) -> float:
66
- return self._positions.get(self._current_symbol, 0.0)
67
-
68
- @property
69
- def avg_price(self) -> float:
70
- return self._avg_prices.get(self._current_symbol, 0.0)
71
-
72
- def submit_order(
73
- self,
74
- order_type: models.OrderType,
75
- side: models.OrderSide,
76
- quantity: float,
77
- limit_price: float | None = None,
78
- stop_price: float | None = None,
79
- ) -> uuid.UUID:
80
- # Uses bar timestamp for backtest compatibility; ts_created tracks real wall-clock time
81
- order_id = uuid.uuid4()
82
-
83
- event = events.OrderSubmission(
84
- ts_event=self._current_ts,
85
- system_order_id=order_id,
86
- symbol=self._current_symbol,
87
- order_type=order_type,
88
- side=side,
89
- quantity=quantity,
90
- limit_price=limit_price,
91
- stop_price=stop_price,
92
- )
93
-
94
- order = models.OrderRecord(
95
- order_id=order_id,
96
- symbol=self._current_symbol,
97
- order_type=order_type,
98
- side=side,
99
- quantity=quantity,
100
- limit_price=limit_price,
101
- stop_price=stop_price,
102
- )
103
-
104
- self._submitted_orders[order_id] = order
105
- self._publish(event)
106
- return order_id
107
-
108
- def submit_modification(
109
- self,
110
- order_id: uuid.UUID,
111
- quantity: float | None = None,
112
- limit_price: float | None = None,
113
- stop_price: float | None = None,
114
- ) -> bool:
115
- original_order = self._pending_orders.get(order_id)
116
- if original_order is None:
117
- return False
118
-
119
- event = events.OrderModification(
120
- ts_event=self._current_ts,
121
- system_order_id=order_id,
122
- symbol=original_order.symbol,
123
- quantity=quantity,
124
- limit_price=limit_price,
125
- stop_price=stop_price,
126
- )
127
-
128
- modified_order = models.OrderRecord(
129
- order_id=order_id,
130
- symbol=original_order.symbol,
131
- order_type=original_order.order_type,
132
- side=original_order.side,
133
- quantity=quantity if quantity is not None else original_order.quantity,
134
- limit_price=(
135
- limit_price if limit_price is not None else original_order.limit_price
136
- ),
137
- stop_price=(
138
- stop_price if stop_price is not None else original_order.stop_price
139
- ),
140
- filled_quantity=original_order.filled_quantity,
141
- )
142
-
143
- self._submitted_modifications[order_id] = modified_order
144
- self._publish(event)
145
- return True
146
-
147
- def submit_cancellation(self, order_id: uuid.UUID) -> bool:
148
- original_order = self._pending_orders.get(order_id)
149
- if original_order is None:
150
- return False
151
-
152
- event = events.OrderCancellation(
153
- ts_event=self._current_ts,
154
- system_order_id=order_id,
155
- symbol=original_order.symbol,
156
- )
157
-
158
- self._submitted_cancellations[order_id] = original_order
159
- self._publish(event)
160
- return True
161
-
162
- def _on_event(self, event: events.EventBase) -> None:
163
- match event:
164
- case events.BarReceived() as bar_event:
165
- self._on_bar_received(bar_event)
166
- case events.OrderSubmissionAccepted() as accepted:
167
- self._on_order_submission_accepted(accepted)
168
- case events.OrderModificationAccepted() as accepted:
169
- self._on_order_modification_accepted(accepted)
170
- case events.OrderCancellationAccepted() as accepted:
171
- self._on_order_cancellation_accepted(accepted)
172
- case events.OrderSubmissionRejected() as rejected:
173
- self._on_order_submission_rejected(rejected)
174
- case events.OrderModificationRejected() as rejected:
175
- self._on_order_modification_rejected(rejected)
176
- case events.OrderCancellationRejected() as rejected:
177
- self._on_order_cancellation_rejected(rejected)
178
- case events.OrderFilled() as filled:
179
- self._on_order_filled(filled)
180
- case events.OrderExpired() as expired:
181
- self._on_order_expired(expired)
182
- case _:
183
- return
184
-
185
- def _on_bar_received(self, event: events.BarReceived) -> None:
186
- if event.symbol not in self.symbols:
187
- return
188
- if event.bar_period != self.bar_period: # type: ignore[attr-defined]
189
- return
190
-
191
- self._current_symbol = event.symbol
192
- self._current_ts = event.ts_event
193
-
194
- for ind in self._indicators:
195
- ind.update(event)
196
-
197
- self._emit_processed_bar(event)
198
- self.on_bar(event)
199
-
200
- def _emit_processed_bar(self, event: events.BarReceived) -> None:
201
- ohlcv_names = {"OPEN", "HIGH", "LOW", "CLOSE", "VOLUME"}
202
-
203
- indicator_values = {
204
- f"{ind.plot_at:02d}_{ind.name}": ind.latest
205
- for ind in self._indicators
206
- if ind.name not in ohlcv_names
207
- }
208
-
209
- processed_bar = events.BarProcessed(
210
- ts_event=event.ts_event,
211
- symbol=event.symbol,
212
- bar_period=event.bar_period,
213
- open=event.open,
214
- high=event.high,
215
- low=event.low,
216
- close=event.close,
217
- volume=event.volume,
218
- indicators=indicator_values,
219
- )
220
-
221
- self._publish(processed_bar)
222
-
223
- def _on_order_submission_accepted(
224
- self, event: events.OrderSubmissionAccepted
225
- ) -> None:
226
- order = self._submitted_orders.pop(event.associated_order_id, None)
227
- if order is not None:
228
- self._pending_orders[event.associated_order_id] = order
229
-
230
- def _on_order_modification_accepted(
231
- self, event: events.OrderModificationAccepted
232
- ) -> None:
233
- modified_order = self._submitted_modifications.pop(
234
- event.associated_order_id, None
235
- )
236
- if modified_order is not None:
237
- self._pending_orders[event.associated_order_id] = modified_order
238
-
239
- def _on_order_cancellation_accepted(
240
- self, event: events.OrderCancellationAccepted
241
- ) -> None:
242
- self._submitted_cancellations.pop(event.associated_order_id, None)
243
- self._pending_orders.pop(event.associated_order_id, None)
244
-
245
- def _on_order_submission_rejected(
246
- self, event: events.OrderSubmissionRejected
247
- ) -> None:
248
- self._submitted_orders.pop(event.associated_order_id, None)
249
-
250
- def _on_order_modification_rejected(
251
- self, event: events.OrderModificationRejected
252
- ) -> None:
253
- self._submitted_modifications.pop(event.associated_order_id, None)
254
-
255
- def _on_order_cancellation_rejected(
256
- self, event: events.OrderCancellationRejected
257
- ) -> None:
258
- self._submitted_cancellations.pop(event.associated_order_id, None)
259
-
260
- def _on_order_filled(self, event: events.OrderFilled) -> None:
261
- # Track partial fills: only remove order when fully filled
262
- order = self._pending_orders.get(event.associated_order_id)
263
- if order:
264
- order.filled_quantity += event.quantity_filled
265
- if order.filled_quantity >= order.quantity:
266
- self._pending_orders.pop(event.associated_order_id)
267
-
268
- fill = models.FillRecord(
269
- fill_id=event.fill_id,
270
- order_id=event.associated_order_id,
271
- symbol=event.symbol,
272
- side=event.side,
273
- quantity=event.quantity_filled,
274
- price=event.fill_price,
275
- commission=event.commission,
276
- ts_event=event.ts_event,
277
- )
278
-
279
- self._fills.setdefault(event.symbol, []).append(fill)
280
- self._update_position(event)
281
-
282
- def _update_position(self, event: events.OrderFilled) -> None:
283
- symbol = event.symbol
284
- fill_qty = event.quantity_filled
285
- fill_price = event.fill_price
286
-
287
- signed_qty = 0.0
288
- match event.side:
289
- case models.OrderSide.BUY:
290
- signed_qty = fill_qty
291
- case models.OrderSide.SELL:
292
- signed_qty = -fill_qty
293
-
294
- old_pos = self._positions.get(symbol, 0.0)
295
- old_avg = self._avg_prices.get(symbol, 0.0)
296
- new_pos = old_pos + signed_qty
297
-
298
- if new_pos == 0.0:
299
- new_avg = 0.0
300
- elif old_pos == 0.0:
301
- new_avg = fill_price
302
- elif (old_pos > 0 and signed_qty > 0) or (old_pos < 0 and signed_qty < 0):
303
- new_avg = (old_avg * abs(old_pos) + fill_price * abs(signed_qty)) / abs(
304
- new_pos
305
- )
306
- else:
307
- if abs(new_pos) <= abs(old_pos):
308
- new_avg = old_avg
309
- else:
310
- new_avg = fill_price
311
-
312
- self._positions[symbol] = new_pos
313
- self._avg_prices[symbol] = new_avg
314
-
315
- def _on_order_expired(self, event: events.OrderExpired) -> None:
316
- self._pending_orders.pop(event.associated_order_id, None)
317
-
318
- # Override to register indicators. Called at end of __init__.
319
- def setup(self) -> None:
320
- pass
321
-
322
- @abc.abstractmethod
323
- def on_bar(self, event: events.BarReceived) -> None:
324
- pass
@@ -1,43 +0,0 @@
1
- from onesecondtrader.core import events, indicators, models
2
- from .base import StrategyBase
3
-
4
-
5
- class SMACrossover(StrategyBase):
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
- }
13
-
14
- def setup(self) -> None:
15
- self.fast_sma = self.add_indicator(
16
- indicators.SimpleMovingAverage(period=self.fast_period) # type: ignore[attr-defined]
17
- )
18
- self.slow_sma = self.add_indicator(
19
- indicators.SimpleMovingAverage(period=self.slow_period) # type: ignore[attr-defined]
20
- )
21
-
22
- def on_bar(self, event: events.BarReceived) -> None:
23
- if (
24
- self.fast_sma[-2] <= self.slow_sma[-2]
25
- and self.fast_sma.latest > self.slow_sma.latest
26
- and self.position <= 0
27
- ):
28
- self.submit_order(
29
- models.OrderType.MARKET,
30
- models.OrderSide.BUY,
31
- self.quantity, # type: ignore[attr-defined]
32
- )
33
-
34
- if (
35
- self.fast_sma[-2] >= self.slow_sma[-2]
36
- and self.fast_sma.latest < self.slow_sma.latest
37
- and self.position >= 0
38
- ):
39
- self.submit_order(
40
- models.OrderType.MARKET,
41
- models.OrderSide.SELL,
42
- self.quantity, # type: ignore[attr-defined]
43
- )
@@ -1,3 +0,0 @@
1
- __all__ = ["app"]
2
-
3
- from .app import app