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