onesecondtrader 0.48.0__tar.gz → 0.50.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.
Files changed (38) hide show
  1. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/PKG-INFO +1 -1
  2. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/pyproject.toml +1 -1
  3. onesecondtrader-0.50.0/src/onesecondtrader/indicators/__init__.py +17 -0
  4. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/indicators/base.py +1 -1
  5. onesecondtrader-0.50.0/src/onesecondtrader/indicators/market_fields.py +166 -0
  6. onesecondtrader-0.50.0/src/onesecondtrader/indicators/moving_averages.py +104 -0
  7. onesecondtrader-0.50.0/src/onesecondtrader/messaging/__init__.py +11 -0
  8. onesecondtrader-0.50.0/src/onesecondtrader/messaging/eventbus.py +96 -0
  9. onesecondtrader-0.50.0/src/onesecondtrader/messaging/subscriber.py +153 -0
  10. onesecondtrader-0.48.0/src/onesecondtrader/indicators/__init__.py +0 -9
  11. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/LICENSE +0 -0
  12. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/README.md +0 -0
  13. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/__init__.py +0 -0
  14. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/__init__.py +0 -0
  15. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/base.py +0 -0
  16. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/market/__init__.py +0 -0
  17. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/market/bar_processed.py +0 -0
  18. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/market/bar_received.py +0 -0
  19. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/orders/__init__.py +0 -0
  20. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/orders/base.py +0 -0
  21. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/orders/expirations.py +0 -0
  22. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/orders/fills.py +0 -0
  23. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/requests/__init__.py +0 -0
  24. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/requests/base.py +0 -0
  25. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/requests/order_cancellation.py +0 -0
  26. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/requests/order_modification.py +0 -0
  27. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/requests/order_submission.py +0 -0
  28. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/responses/__init__.py +0 -0
  29. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/responses/base.py +0 -0
  30. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/responses/cancellations.py +0 -0
  31. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/responses/modifications.py +0 -0
  32. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/events/responses/orders.py +0 -0
  33. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/__init__.py +0 -0
  34. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/bar_fields.py +0 -0
  35. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/bar_period.py +0 -0
  36. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/order_types.py +0 -0
  37. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/rejection_reasons.py +0 -0
  38. {onesecondtrader-0.48.0 → onesecondtrader-0.50.0}/src/onesecondtrader/models/trade_sides.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onesecondtrader
3
- Version: 0.48.0
3
+ Version: 0.50.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.48.0"
3
+ version = "0.50.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,17 @@
1
+ """
2
+ Provides a library of common technical indicators and a base class for creating custom ones.
3
+ """
4
+
5
+ from .base import IndicatorBase
6
+ from .moving_averages import SimpleMovingAverage
7
+ from .market_fields import Open, High, Low, Close, Volume
8
+
9
+ __all__ = [
10
+ "IndicatorBase",
11
+ "SimpleMovingAverage",
12
+ "Open",
13
+ "High",
14
+ "Low",
15
+ "Close",
16
+ "Volume",
17
+ ]
@@ -9,7 +9,7 @@ import numpy as np
9
9
  from onesecondtrader import events
10
10
 
11
11
 
12
- class Indicator(abc.ABC):
12
+ class IndicatorBase(abc.ABC):
13
13
  """
14
14
  Base class for scalar technical indicators with per-symbol history.
15
15
 
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+
5
+ from onesecondtrader import events, indicators
6
+
7
+
8
+ class Open(indicators.IndicatorBase):
9
+ """
10
+ Open price indicator.
11
+
12
+ This indicator exposes the open price of each incoming market bar as a scalar time series.
13
+ Values are stored per symbol and can be accessed historically via the indicator interface.
14
+ """
15
+
16
+ @property
17
+ def name(self) -> str:
18
+ """
19
+ Canonical indicator name.
20
+
21
+ Returns:
22
+ Fixed identifier for the open price indicator.
23
+ """
24
+ return "OPEN"
25
+
26
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
27
+ """
28
+ Extract the open price from an incoming market bar.
29
+
30
+ Parameters:
31
+ incoming_bar:
32
+ Market bar used as input.
33
+
34
+ Returns:
35
+ Open price of the bar.
36
+ """
37
+ return incoming_bar.open
38
+
39
+
40
+ class High(indicators.IndicatorBase):
41
+ """
42
+ High price indicator.
43
+
44
+ This indicator exposes the high price of each incoming market bar as a scalar time series.
45
+ Values are stored per symbol and can be accessed historically via the indicator interface.
46
+ """
47
+
48
+ @property
49
+ def name(self) -> str:
50
+ """
51
+ Canonical indicator name.
52
+
53
+ Returns:
54
+ Fixed identifier for the high price indicator.
55
+ """
56
+ return "HIGH"
57
+
58
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
59
+ """
60
+ Extract the high price from an incoming market bar.
61
+
62
+ Parameters:
63
+ incoming_bar:
64
+ Market bar used as input.
65
+
66
+ Returns:
67
+ High price of the bar.
68
+ """
69
+ return incoming_bar.high
70
+
71
+
72
+ class Low(indicators.IndicatorBase):
73
+ """
74
+ Low price indicator.
75
+
76
+ This indicator exposes the low price of each incoming market bar as a scalar time series.
77
+ Values are stored per symbol and can be accessed historically via the indicator interface.
78
+ """
79
+
80
+ @property
81
+ def name(self) -> str:
82
+ """
83
+ Canonical indicator name.
84
+
85
+ Returns:
86
+ Fixed identifier for the low price indicator.
87
+ """
88
+ return "LOW"
89
+
90
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
91
+ """
92
+ Extract the low price from an incoming market bar.
93
+
94
+ Parameters:
95
+ incoming_bar:
96
+ Market bar used as input.
97
+
98
+ Returns:
99
+ Low price of the bar.
100
+ """
101
+ return incoming_bar.low
102
+
103
+
104
+ class Close(indicators.IndicatorBase):
105
+ """
106
+ Close price indicator.
107
+
108
+ This indicator exposes the close price of each incoming market bar as a scalar time series.
109
+ Values are stored per symbol and can be accessed historically via the indicator interface.
110
+ """
111
+
112
+ @property
113
+ def name(self) -> str:
114
+ """
115
+ Canonical indicator name.
116
+
117
+ Returns:
118
+ Fixed identifier for the close price indicator.
119
+ """
120
+ return "CLOSE"
121
+
122
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
123
+ """
124
+ Extract the close price from an incoming market bar.
125
+
126
+ Parameters:
127
+ incoming_bar:
128
+ Market bar used as input.
129
+
130
+ Returns:
131
+ Close price of the bar.
132
+ """
133
+ return incoming_bar.close
134
+
135
+
136
+ class Volume(indicators.IndicatorBase):
137
+ """
138
+ Volume indicator.
139
+
140
+ This indicator exposes the traded volume of each incoming market bar as a scalar time series.
141
+ Values are stored per symbol and can be accessed historically via the indicator interface.
142
+ Missing volume values yield `numpy.nan`.
143
+ """
144
+
145
+ @property
146
+ def name(self) -> str:
147
+ """
148
+ Canonical indicator name.
149
+
150
+ Returns:
151
+ Fixed identifier for the volume indicator.
152
+ """
153
+ return "VOLUME"
154
+
155
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
156
+ """
157
+ Extract the volume from an incoming market bar.
158
+
159
+ Parameters:
160
+ incoming_bar:
161
+ Market bar used as input.
162
+
163
+ Returns:
164
+ Volume of the bar, or `numpy.nan` if unavailable.
165
+ """
166
+ return float(incoming_bar.volume) if incoming_bar.volume is not None else np.nan
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import collections
4
+ import numpy as np
5
+
6
+ from onesecondtrader import events, indicators, models
7
+
8
+
9
+ class SimpleMovingAverage(indicators.IndicatorBase):
10
+ """
11
+ Simple Moving Average (SMA) indicator.
12
+
13
+ This indicator computes the arithmetic mean of a selected bar field over a fixed rolling window.
14
+ One scalar value is produced per incoming bar and stored per symbol.
15
+
16
+ The rolling window is maintained independently for each symbol.
17
+ Until the window is fully populated, the indicator yields `numpy.nan`.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ period: int = 200,
23
+ max_history: int = 100,
24
+ bar_field: models.BarField = models.BarField.CLOSE,
25
+ plot_at: int = 0,
26
+ ) -> None:
27
+ """
28
+ Parameters:
29
+ period:
30
+ Window size used to compute the moving average.
31
+ max_history:
32
+ Maximum number of computed indicator values retained per symbol.
33
+ bar_field:
34
+ Bar field used as the input series.
35
+ plot_at:
36
+ Opaque plotting identifier forwarded to the charting backend.
37
+ """
38
+ super().__init__(max_history=max_history, plot_at=plot_at)
39
+
40
+ self.period: int = max(1, int(period))
41
+ self.bar_field: models.BarField = bar_field
42
+ self._window: dict[str, collections.deque[float]] = {}
43
+
44
+ @property
45
+ def name(self) -> str:
46
+ """
47
+ Canonical indicator name.
48
+
49
+ Returns:
50
+ Identifier encoding the indicator type, period, and bar field.
51
+ """
52
+ return f"SMA_{self.period}_{self.bar_field.name}"
53
+
54
+ def _compute_indicator(self, incoming_bar: events.market.BarReceived) -> float:
55
+ """
56
+ Compute the simple moving average for a single received bar.
57
+
58
+ Parameters:
59
+ incoming_bar:
60
+ Market bar used as input for the computation.
61
+
62
+ Returns:
63
+ Simple moving average value, or `numpy.nan` if the rolling window is not yet fully populated.
64
+ """
65
+ symbol = incoming_bar.symbol
66
+ if symbol not in self._window:
67
+ self._window[symbol] = collections.deque(maxlen=self.period)
68
+
69
+ window = self._window[symbol]
70
+ value = self._extract_field(incoming_bar)
71
+ window.append(value)
72
+
73
+ if len(window) < self.period:
74
+ return np.nan
75
+ return sum(window) / self.period
76
+
77
+ def _extract_field(self, incoming_bar: events.market.BarReceived) -> float:
78
+ """
79
+ Extract the configured bar field from an incoming bar.
80
+
81
+ Parameters:
82
+ incoming_bar:
83
+ Market bar providing the input data.
84
+
85
+ Returns:
86
+ Extracted field value, or `numpy.nan` if unavailable.
87
+ """
88
+ match self.bar_field:
89
+ case models.BarField.OPEN:
90
+ return incoming_bar.open
91
+ case models.BarField.HIGH:
92
+ return incoming_bar.high
93
+ case models.BarField.LOW:
94
+ return incoming_bar.low
95
+ case models.BarField.CLOSE:
96
+ return incoming_bar.close
97
+ case models.BarField.VOLUME:
98
+ return (
99
+ float(incoming_bar.volume)
100
+ if incoming_bar.volume is not None
101
+ else np.nan
102
+ )
103
+ case _:
104
+ return incoming_bar.close
@@ -0,0 +1,11 @@
1
+ """
2
+ Provides the infrastructure for event-based communication between system components.
3
+ """
4
+
5
+ from .eventbus import EventBus
6
+ from .subscriber import Subscriber
7
+
8
+ __all__ = [
9
+ "EventBus",
10
+ "Subscriber",
11
+ ]
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import collections
4
+ import threading
5
+ import typing
6
+
7
+ from onesecondtrader import events
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from .subscriber import Subscriber
11
+
12
+
13
+ class EventBus:
14
+ """
15
+ Event dispatch mechanism for propagating event objects to subscribers.
16
+
17
+ The event bus maintains subscriptions between subscribers and concrete event types.
18
+ Events published to the bus are synchronously delivered to all subscribers registered for the exact event type.
19
+
20
+ Subscription management and event publication are thread-safe.
21
+ Event delivery itself occurs outside the internal lock.
22
+ """
23
+
24
+ def __init__(self) -> None:
25
+ """
26
+ Initialize an empty event bus.
27
+
28
+ The bus starts with no registered subscribers and no active subscriptions.
29
+ """
30
+ self._per_event_subscriptions: collections.defaultdict[
31
+ type[events.EventBase], set[Subscriber]
32
+ ] = collections.defaultdict(set)
33
+ self._subscribers: set[Subscriber] = set()
34
+ self._lock: threading.Lock = threading.Lock()
35
+
36
+ def subscribe(
37
+ self,
38
+ subscriber: Subscriber,
39
+ event_type: type[events.EventBase],
40
+ ) -> None:
41
+ """
42
+ Register a subscriber for a specific event type.
43
+
44
+ The subscriber will receive all future events whose concrete type matches `event_type`.
45
+
46
+ Parameters:
47
+ subscriber:
48
+ Object receiving published events.
49
+ event_type:
50
+ Concrete event class the subscriber is interested in.
51
+ """
52
+ with self._lock:
53
+ self._subscribers.add(subscriber)
54
+ self._per_event_subscriptions[event_type].add(subscriber)
55
+
56
+ def unsubscribe(self, subscriber: Subscriber) -> None:
57
+ """
58
+ Remove a subscriber from all event subscriptions.
59
+
60
+ After unsubscription, the subscriber will no longer receive any events published on this bus.
61
+
62
+ Parameters:
63
+ subscriber:
64
+ Subscriber to remove.
65
+ """
66
+ with self._lock:
67
+ for set_of_event_subscribers in self._per_event_subscriptions.values():
68
+ set_of_event_subscribers.discard(subscriber)
69
+ self._subscribers.discard(subscriber)
70
+
71
+ def publish(self, event: events.EventBase) -> None:
72
+ """
73
+ Publish an event to all subscribed listeners.
74
+
75
+ Subscribers are matched strictly by the concrete type of the event.
76
+ Parent classes and inheritance relationships are not considered.
77
+
78
+ Parameters:
79
+ event:
80
+ Event instance to dispatch.
81
+ """
82
+ with self._lock:
83
+ subscribers = self._per_event_subscriptions[type(event)].copy()
84
+ for subscriber in subscribers:
85
+ subscriber.receive(event)
86
+
87
+ def wait_until_system_idle(self) -> None:
88
+ """
89
+ Block until all subscribers report an idle state.
90
+
91
+ This method delegates to each subscriber's `wait_until_idle` method and returns only after all subscribers have completed any pending work.
92
+ """
93
+ with self._lock:
94
+ subscribers = self._subscribers.copy()
95
+ for subscriber in subscribers:
96
+ subscriber.wait_until_idle()
@@ -0,0 +1,153 @@
1
+ import abc
2
+ import queue
3
+ import threading
4
+
5
+ from onesecondtrader import events, messaging
6
+
7
+
8
+ class Subscriber(abc.ABC):
9
+ """
10
+ Abstract base class for event bus subscribers.
11
+
12
+ A subscriber receives events from an event bus and processes them asynchronously in a dedicated worker thread.
13
+ Incoming events are queued and handled sequentially.
14
+
15
+ Subclasses implement `_on_event` to define event-specific behavior.
16
+ """
17
+
18
+ def __init__(self, event_bus: messaging.EventBus) -> None:
19
+ """
20
+ Initialize the subscriber and start its event-processing thread.
21
+
22
+ Parameters:
23
+ event_bus:
24
+ Event bus used for subscribing to and publishing events.
25
+ """
26
+ self._event_bus = event_bus
27
+ self._queue: queue.Queue[events.EventBase | None] = queue.Queue()
28
+
29
+ self._running: threading.Event = threading.Event()
30
+ self._running.set()
31
+
32
+ self._thread = threading.Thread(
33
+ target=self._event_loop, name=self.__class__.__name__
34
+ )
35
+ self._thread.start()
36
+
37
+ def receive(self, event: events.EventBase) -> None:
38
+ """
39
+ Receive an event from the event bus.
40
+
41
+ The event is enqueued for asynchronous processing if the subscriber is running.
42
+
43
+ Parameters:
44
+ event:
45
+ Event instance delivered by the event bus.
46
+ """
47
+ if self._running.is_set():
48
+ self._queue.put(event)
49
+
50
+ def wait_until_idle(self) -> None:
51
+ """
52
+ Block until all queued events have been processed.
53
+
54
+ If the subscriber is not running, this method returns immediately.
55
+ """
56
+ if not self._running.is_set():
57
+ return
58
+
59
+ self._queue.join()
60
+
61
+ def shutdown(self) -> None:
62
+ """
63
+ Shut down the subscriber and stop event processing.
64
+
65
+ The subscriber is unsubscribed from the event bus, its worker thread is signaled to terminate, and all pending events are processed before shutdown completes.
66
+ """
67
+ if not self._running.is_set():
68
+ return
69
+
70
+ self._event_bus.unsubscribe(self)
71
+ self._running.clear()
72
+ self._queue.put(None)
73
+
74
+ if threading.current_thread() is not self._thread:
75
+ self._thread.join()
76
+
77
+ def _subscribe(self, *event_types: type[events.EventBase]) -> None:
78
+ """
79
+ Subscribe this subscriber to one or more event types.
80
+
81
+ Parameters:
82
+ *event_types:
83
+ Concrete event classes to subscribe to.
84
+ """
85
+ for event_type in event_types:
86
+ self._event_bus.subscribe(self, event_type)
87
+
88
+ def _publish(self, event: events.EventBase) -> None:
89
+ """
90
+ Publish an event to the event bus.
91
+
92
+ Parameters:
93
+ event:
94
+ Event instance to publish.
95
+ """
96
+ self._event_bus.publish(event)
97
+
98
+ def _event_loop(self) -> None:
99
+ """
100
+ Internal worker loop for processing queued events.
101
+
102
+ This method runs in a dedicated thread and should not be called directly.
103
+ """
104
+ while True:
105
+ event = self._queue.get()
106
+
107
+ if event is None:
108
+ self._queue.task_done()
109
+ break
110
+
111
+ try:
112
+ self._on_event(event)
113
+ except Exception as exc:
114
+ self._on_exception(exc)
115
+ finally:
116
+ self._queue.task_done()
117
+
118
+ self._cleanup()
119
+
120
+ def _on_exception(self, exc: Exception) -> None:
121
+ """
122
+ Handle an exception raised during event processing.
123
+
124
+ Subclasses may override this method to implement logging or recovery behavior.
125
+ The default implementation ignores the exception.
126
+
127
+ Parameters:
128
+ exc:
129
+ Exception raised while processing an event.
130
+ """
131
+ pass
132
+
133
+ def _cleanup(self) -> None:
134
+ """
135
+ Perform cleanup after the event loop terminates.
136
+
137
+ Subclasses may override this method to release resources or emit shutdown notifications.
138
+ """
139
+ pass
140
+
141
+ @abc.abstractmethod
142
+ def _on_event(self, event: events.EventBase) -> None:
143
+ """
144
+ Handle a single event.
145
+
146
+ This method is invoked sequentially for each event received by the subscriber.
147
+ Implementations must not block indefinitely, as `wait_until_idle` relies on timely completion.
148
+
149
+ Parameters:
150
+ event:
151
+ Event instance to handle.
152
+ """
153
+ ...
@@ -1,9 +0,0 @@
1
- """
2
- Provides a library of common technical indicators and a base class for creating custom ones.
3
- """
4
-
5
- from .base import Indicator
6
-
7
- __all__ = [
8
- "Indicator",
9
- ]