onesecondtrader 0.36.0__py3-none-any.whl → 0.37.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.
@@ -1,22 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import dataclasses
4
-
5
- from onesecondtrader import models
6
- from . import bases
7
-
8
-
9
- @dataclasses.dataclass(kw_only=True, frozen=True)
10
- class BarReceived(bases.MarketEvent):
11
- symbol: str
12
- bar_period: models.data.BarPeriod
13
- open: float
14
- high: float
15
- low: float
16
- close: float
17
- volume: int | None = None
18
-
19
-
20
- @dataclasses.dataclass(kw_only=True, frozen=True)
21
- class BarProcessed(BarReceived):
22
- indicators: dict[str, float] = dataclasses.field(default_factory=dict)
@@ -1,31 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import dataclasses
4
- import uuid
5
-
6
- from onesecondtrader import models
7
- from . import bases
8
-
9
-
10
- @dataclasses.dataclass(kw_only=True, frozen=True)
11
- class OrderSubmission(bases.BrokerRequestEvent):
12
- system_order_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
13
- symbol: str
14
- order_type: models.orders.OrderType
15
- side: models.orders.OrderSide
16
- quantity: float
17
- limit_price: float | None = None
18
- stop_price: float | None = None
19
-
20
-
21
- @dataclasses.dataclass(kw_only=True, frozen=True)
22
- class OrderCancellation(bases.BrokerRequestEvent):
23
- symbol: str
24
-
25
-
26
- @dataclasses.dataclass(kw_only=True, frozen=True)
27
- class OrderModification(bases.BrokerRequestEvent):
28
- symbol: str
29
- quantity: float | None = None
30
- limit_price: float | None = None
31
- stop_price: float | None = None
@@ -1,54 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import dataclasses
4
- import uuid
5
-
6
- from onesecondtrader import models
7
- from . import bases
8
-
9
-
10
- @dataclasses.dataclass(kw_only=True, frozen=True)
11
- class OrderSubmissionAccepted(bases.BrokerResponseEvent):
12
- broker_order_id: str | None = None
13
-
14
-
15
- @dataclasses.dataclass(kw_only=True, frozen=True)
16
- class OrderSubmissionRejected(bases.BrokerResponseEvent):
17
- reason: str | None = None
18
-
19
-
20
- @dataclasses.dataclass(kw_only=True, frozen=True)
21
- class OrderModificationAccepted(bases.BrokerResponseEvent):
22
- broker_order_id: str | None = None
23
-
24
-
25
- @dataclasses.dataclass(kw_only=True, frozen=True)
26
- class OrderModificationRejected(bases.BrokerResponseEvent):
27
- reason: str | None = None
28
-
29
-
30
- @dataclasses.dataclass(kw_only=True, frozen=True)
31
- class OrderCancellationAccepted(bases.BrokerResponseEvent):
32
- pass
33
-
34
-
35
- @dataclasses.dataclass(kw_only=True, frozen=True)
36
- class OrderCancellationRejected(bases.BrokerResponseEvent):
37
- reason: str | None = None
38
-
39
-
40
- @dataclasses.dataclass(kw_only=True, frozen=True)
41
- class OrderFilled(bases.BrokerResponseEvent):
42
- fill_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
43
- broker_fill_id: str | None = None
44
- symbol: str
45
- side: models.orders.OrderSide
46
- quantity_filled: float
47
- fill_price: float
48
- commission: float
49
- exchange: str = "SIMULATED"
50
-
51
-
52
- @dataclasses.dataclass(kw_only=True, frozen=True)
53
- class OrderExpired(bases.BrokerResponseEvent):
54
- pass
@@ -1,13 +0,0 @@
1
- __all__ = [
2
- "Indicator",
3
- "Open",
4
- "High",
5
- "Low",
6
- "Close",
7
- "Volume",
8
- "SimpleMovingAverage",
9
- ]
10
-
11
- from .base import Indicator
12
- from .bar import Open, High, Low, Close, Volume
13
- from .averages import SimpleMovingAverage
@@ -1,56 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import collections
4
-
5
- import numpy as np
6
-
7
- from onesecondtrader import events, models
8
- from .base import Indicator
9
-
10
-
11
- class SimpleMovingAverage(Indicator):
12
- def __init__(
13
- self,
14
- period: int = 200,
15
- max_history: int = 100,
16
- input_source: models.InputSource = models.InputSource.CLOSE,
17
- plot_at: int = 0,
18
- ) -> None:
19
- super().__init__(max_history=max_history, plot_at=plot_at)
20
- self.period: int = max(1, int(period))
21
- self.input_source: models.InputSource = input_source
22
- self._window: dict[str, collections.deque[float]] = {}
23
-
24
- @property
25
- def name(self) -> str:
26
- return f"SMA_{self.period}_{self.input_source.name}"
27
-
28
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
29
- symbol = incoming_bar.symbol
30
- if symbol not in self._window:
31
- self._window[symbol] = collections.deque(maxlen=self.period)
32
- window = self._window[symbol]
33
- value = self._extract_input(incoming_bar)
34
- window.append(value)
35
- if len(window) < self.period:
36
- return np.nan
37
- return sum(window) / self.period
38
-
39
- def _extract_input(self, incoming_bar: events.BarReceived) -> float:
40
- match self.input_source:
41
- case models.InputSource.OPEN:
42
- return incoming_bar.open
43
- case models.InputSource.HIGH:
44
- return incoming_bar.high
45
- case models.InputSource.LOW:
46
- return incoming_bar.low
47
- case models.InputSource.CLOSE:
48
- return incoming_bar.close
49
- case models.InputSource.VOLUME:
50
- return (
51
- float(incoming_bar.volume)
52
- if incoming_bar.volume is not None
53
- else np.nan
54
- )
55
- case _:
56
- return incoming_bar.close
@@ -1,47 +0,0 @@
1
- from onesecondtrader import events
2
- from .base import Indicator
3
-
4
-
5
- class Open(Indicator):
6
- @property
7
- def name(self) -> str:
8
- return "OPEN"
9
-
10
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
11
- return incoming_bar.open
12
-
13
-
14
- class High(Indicator):
15
- @property
16
- def name(self) -> str:
17
- return "HIGH"
18
-
19
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
20
- return incoming_bar.high
21
-
22
-
23
- class Low(Indicator):
24
- @property
25
- def name(self) -> str:
26
- return "LOW"
27
-
28
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
29
- return incoming_bar.low
30
-
31
-
32
- class Close(Indicator):
33
- @property
34
- def name(self) -> str:
35
- return "CLOSE"
36
-
37
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
38
- return incoming_bar.close
39
-
40
-
41
- class Volume(Indicator):
42
- @property
43
- def name(self) -> str:
44
- return "VOLUME"
45
-
46
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
47
- return float(incoming_bar.volume) if incoming_bar.volume is not None else 0.0
@@ -1,60 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import abc
4
- import collections
5
- import threading
6
-
7
- import numpy as np
8
-
9
- from onesecondtrader import events
10
-
11
-
12
- class Indicator(abc.ABC):
13
- def __init__(self, max_history: int = 100, plot_at: int = 99) -> None:
14
- self._lock = threading.Lock()
15
- self._max_history = max(1, int(max_history))
16
- self._current_symbol: str = ""
17
- self._history_data: dict[str, collections.deque[float]] = {}
18
- self._plot_at = plot_at
19
-
20
- @property
21
- @abc.abstractmethod
22
- def name(self) -> str:
23
- pass
24
-
25
- @abc.abstractmethod
26
- def _compute_indicator(self, incoming_bar: events.BarReceived) -> float:
27
- pass
28
-
29
- def update(self, incoming_bar: events.BarReceived) -> None:
30
- symbol = incoming_bar.symbol
31
- self._current_symbol = symbol
32
- value = self._compute_indicator(incoming_bar)
33
- with self._lock:
34
- if symbol not in self._history_data:
35
- self._history_data[symbol] = collections.deque(maxlen=self._max_history)
36
- self._history_data[symbol].append(value)
37
-
38
- @property
39
- def latest(self) -> float:
40
- with self._lock:
41
- h = self._history_data.get(self._current_symbol, collections.deque())
42
- return h[-1] if h else np.nan
43
-
44
- @property
45
- def history(self) -> collections.deque[float]:
46
- with self._lock:
47
- h = self._history_data.get(self._current_symbol, collections.deque())
48
- return collections.deque(h, maxlen=self._max_history)
49
-
50
- def __getitem__(self, index: int) -> float:
51
- # Returns np.nan on out-of-bounds access. Since np.nan comparisons always
52
- # return False, strategies can skip explicit length checks.
53
- try:
54
- return self.history[index]
55
- except IndexError:
56
- return np.nan
57
-
58
- @property
59
- def plot_at(self) -> int:
60
- return self._plot_at
@@ -1,7 +0,0 @@
1
- __all__ = [
2
- "EventBus",
3
- "Subscriber",
4
- ]
5
-
6
- from .eventbus import EventBus
7
- from .subscriber import Subscriber
@@ -1,47 +0,0 @@
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
- def __init__(self) -> None:
15
- self._per_event_subscriptions: collections.defaultdict[
16
- type[events.bases.EventBase], set[Subscriber]
17
- ] = collections.defaultdict(set)
18
- self._subscribers: set[Subscriber] = set()
19
- self._lock: threading.Lock = threading.Lock()
20
-
21
- def subscribe(
22
- self,
23
- subscriber: Subscriber,
24
- event_type: type[events.bases.EventBase],
25
- ) -> None:
26
- with self._lock:
27
- self._subscribers.add(subscriber)
28
- self._per_event_subscriptions[event_type].add(subscriber)
29
-
30
- def unsubscribe(self, subscriber: Subscriber) -> None:
31
- with self._lock:
32
- for set_of_event_subscribers in self._per_event_subscriptions.values():
33
- set_of_event_subscribers.discard(subscriber)
34
- self._subscribers.discard(subscriber)
35
-
36
- def publish(self, event: events.bases.EventBase) -> None:
37
- # Intentionally matches exact event types only, not parent classes
38
- with self._lock:
39
- subscribers = self._per_event_subscriptions[type(event)].copy()
40
- for subscriber in subscribers:
41
- subscriber.receive(event)
42
-
43
- def wait_until_system_idle(self) -> None:
44
- with self._lock:
45
- subscribers = self._subscribers.copy()
46
- for subscriber in subscribers:
47
- subscriber.wait_until_idle()
@@ -1,69 +0,0 @@
1
- import abc
2
- import queue
3
- import threading
4
-
5
- from onesecondtrader import events
6
- from .eventbus import EventBus
7
-
8
-
9
- class Subscriber(abc.ABC):
10
- def __init__(self, event_bus: EventBus) -> None:
11
- self._event_bus = event_bus
12
- self._queue: queue.Queue[events.bases.EventBase | None] = queue.Queue()
13
- self._running: threading.Event = threading.Event()
14
- self._running.set()
15
- self._thread = threading.Thread(
16
- target=self._event_loop, name=self.__class__.__name__
17
- )
18
- self._thread.start()
19
-
20
- def receive(self, event: events.bases.EventBase) -> None:
21
- if self._running.is_set():
22
- self._queue.put(event)
23
-
24
- def wait_until_idle(self) -> None:
25
- if not self._running.is_set():
26
- return
27
- self._queue.join()
28
-
29
- def shutdown(self) -> None:
30
- if not self._running.is_set():
31
- return
32
- self._event_bus.unsubscribe(self)
33
- self._running.clear()
34
- self._queue.put(None)
35
- if threading.current_thread() is not self._thread:
36
- self._thread.join()
37
-
38
- def _subscribe(self, *event_types: type[events.bases.EventBase]) -> None:
39
- for event_type in event_types:
40
- self._event_bus.subscribe(self, event_type)
41
-
42
- def _publish(self, event: events.bases.EventBase) -> None:
43
- self._event_bus.publish(event)
44
-
45
- def _event_loop(self) -> None:
46
- while True:
47
- event = self._queue.get()
48
- if event is None:
49
- self._queue.task_done()
50
- break
51
- try:
52
- self._on_event(event)
53
- except Exception as exc:
54
- self._on_exception(exc)
55
- finally:
56
- self._queue.task_done()
57
- self._cleanup()
58
-
59
- def _on_exception(self, exc: Exception) -> None:
60
- # Override in subclass to log or handle exceptions
61
- pass
62
-
63
- def _cleanup(self) -> None:
64
- pass
65
-
66
- @abc.abstractmethod
67
- def _on_event(self, event: events.bases.EventBase) -> None:
68
- # Must not block indefinitely; wait_until_idle() has no timeout
69
- ...
@@ -1,12 +0,0 @@
1
- __all__ = [
2
- "BarPeriod",
3
- "InputSource",
4
- "OrderSide",
5
- "OrderType",
6
- "OrderRecord",
7
- "FillRecord",
8
- ]
9
-
10
- from .data import BarPeriod, InputSource
11
- from .orders import OrderSide, OrderType
12
- from .records import OrderRecord, FillRecord
@@ -1,18 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import enum
4
-
5
-
6
- class BarPeriod(enum.Enum):
7
- SECOND = enum.auto()
8
- MINUTE = enum.auto()
9
- HOUR = enum.auto()
10
- DAY = enum.auto()
11
-
12
-
13
- class InputSource(enum.Enum):
14
- OPEN = enum.auto()
15
- HIGH = enum.auto()
16
- LOW = enum.auto()
17
- CLOSE = enum.auto()
18
- VOLUME = enum.auto()
@@ -1,15 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import enum
4
-
5
-
6
- class OrderType(enum.Enum):
7
- LIMIT = enum.auto()
8
- MARKET = enum.auto()
9
- STOP = enum.auto()
10
- STOP_LIMIT = enum.auto()
11
-
12
-
13
- class OrderSide(enum.Enum):
14
- BUY = enum.auto()
15
- SELL = enum.auto()
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import dataclasses
4
- import uuid
5
-
6
- import pandas as pd
7
-
8
- from . import orders
9
-
10
-
11
- @dataclasses.dataclass
12
- class OrderRecord:
13
- order_id: uuid.UUID
14
- symbol: str
15
- order_type: orders.OrderType
16
- side: orders.OrderSide
17
- quantity: float
18
- limit_price: float | None = None
19
- stop_price: float | None = None
20
- filled_quantity: float = 0.0
21
-
22
-
23
- @dataclasses.dataclass
24
- class FillRecord:
25
- fill_id: uuid.UUID
26
- order_id: uuid.UUID
27
- symbol: str
28
- side: orders.OrderSide
29
- quantity: float
30
- price: float
31
- commission: float
32
- ts_event: pd.Timestamp
@@ -1,7 +0,0 @@
1
- __all__ = [
2
- "StrategyBase",
3
- "SMACrossover",
4
- ]
5
-
6
- from .base import StrategyBase
7
- from .sma_crossover import SMACrossover