signalflow-trading 0.2.1__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 (90) hide show
  1. signalflow/__init__.py +21 -0
  2. signalflow/analytics/__init__.py +0 -0
  3. signalflow/core/__init__.py +46 -0
  4. signalflow/core/base_mixin.py +232 -0
  5. signalflow/core/containers/__init__.py +21 -0
  6. signalflow/core/containers/order.py +216 -0
  7. signalflow/core/containers/portfolio.py +211 -0
  8. signalflow/core/containers/position.py +296 -0
  9. signalflow/core/containers/raw_data.py +167 -0
  10. signalflow/core/containers/raw_data_view.py +169 -0
  11. signalflow/core/containers/signals.py +198 -0
  12. signalflow/core/containers/strategy_state.py +147 -0
  13. signalflow/core/containers/trade.py +112 -0
  14. signalflow/core/decorators.py +103 -0
  15. signalflow/core/enums.py +270 -0
  16. signalflow/core/registry.py +322 -0
  17. signalflow/core/rolling_aggregator.py +362 -0
  18. signalflow/core/signal_transforms/__init__.py +5 -0
  19. signalflow/core/signal_transforms/base_signal_transform.py +186 -0
  20. signalflow/data/__init__.py +11 -0
  21. signalflow/data/raw_data_factory.py +225 -0
  22. signalflow/data/raw_store/__init__.py +7 -0
  23. signalflow/data/raw_store/base.py +271 -0
  24. signalflow/data/raw_store/duckdb_stores.py +696 -0
  25. signalflow/data/source/__init__.py +10 -0
  26. signalflow/data/source/base.py +300 -0
  27. signalflow/data/source/binance.py +442 -0
  28. signalflow/data/strategy_store/__init__.py +8 -0
  29. signalflow/data/strategy_store/base.py +278 -0
  30. signalflow/data/strategy_store/duckdb.py +409 -0
  31. signalflow/data/strategy_store/schema.py +36 -0
  32. signalflow/detector/__init__.py +7 -0
  33. signalflow/detector/adapter/__init__.py +5 -0
  34. signalflow/detector/adapter/pandas_detector.py +46 -0
  35. signalflow/detector/base.py +390 -0
  36. signalflow/detector/sma_cross.py +105 -0
  37. signalflow/feature/__init__.py +16 -0
  38. signalflow/feature/adapter/__init__.py +5 -0
  39. signalflow/feature/adapter/pandas_feature_extractor.py +54 -0
  40. signalflow/feature/base.py +330 -0
  41. signalflow/feature/feature_set.py +286 -0
  42. signalflow/feature/oscillator/__init__.py +5 -0
  43. signalflow/feature/oscillator/rsi_extractor.py +42 -0
  44. signalflow/feature/pandasta/__init__.py +10 -0
  45. signalflow/feature/pandasta/pandas_ta_extractor.py +141 -0
  46. signalflow/feature/pandasta/top_pandasta_extractors.py +64 -0
  47. signalflow/feature/smoother/__init__.py +5 -0
  48. signalflow/feature/smoother/sma_extractor.py +46 -0
  49. signalflow/strategy/__init__.py +9 -0
  50. signalflow/strategy/broker/__init__.py +15 -0
  51. signalflow/strategy/broker/backtest.py +172 -0
  52. signalflow/strategy/broker/base.py +186 -0
  53. signalflow/strategy/broker/executor/__init__.py +9 -0
  54. signalflow/strategy/broker/executor/base.py +35 -0
  55. signalflow/strategy/broker/executor/binance_spot.py +12 -0
  56. signalflow/strategy/broker/executor/virtual_spot.py +81 -0
  57. signalflow/strategy/broker/realtime_spot.py +12 -0
  58. signalflow/strategy/component/__init__.py +9 -0
  59. signalflow/strategy/component/base.py +65 -0
  60. signalflow/strategy/component/entry/__init__.py +7 -0
  61. signalflow/strategy/component/entry/fixed_size.py +57 -0
  62. signalflow/strategy/component/entry/signal.py +127 -0
  63. signalflow/strategy/component/exit/__init__.py +5 -0
  64. signalflow/strategy/component/exit/time_based.py +47 -0
  65. signalflow/strategy/component/exit/tp_sl.py +80 -0
  66. signalflow/strategy/component/metric/__init__.py +8 -0
  67. signalflow/strategy/component/metric/main_metrics.py +181 -0
  68. signalflow/strategy/runner/__init__.py +8 -0
  69. signalflow/strategy/runner/backtest_runner.py +208 -0
  70. signalflow/strategy/runner/base.py +19 -0
  71. signalflow/strategy/runner/optimized_backtest_runner.py +178 -0
  72. signalflow/strategy/runner/realtime_runner.py +0 -0
  73. signalflow/target/__init__.py +14 -0
  74. signalflow/target/adapter/__init__.py +5 -0
  75. signalflow/target/adapter/pandas_labeler.py +45 -0
  76. signalflow/target/base.py +409 -0
  77. signalflow/target/fixed_horizon_labeler.py +93 -0
  78. signalflow/target/static_triple_barrier.py +162 -0
  79. signalflow/target/triple_barrier.py +188 -0
  80. signalflow/utils/__init__.py +7 -0
  81. signalflow/utils/import_utils.py +11 -0
  82. signalflow/utils/tune_utils.py +19 -0
  83. signalflow/validator/__init__.py +6 -0
  84. signalflow/validator/base.py +139 -0
  85. signalflow/validator/sklearn_validator.py +527 -0
  86. signalflow_trading-0.2.1.dist-info/METADATA +149 -0
  87. signalflow_trading-0.2.1.dist-info/RECORD +90 -0
  88. signalflow_trading-0.2.1.dist-info/WHEEL +5 -0
  89. signalflow_trading-0.2.1.dist-info/licenses/LICENSE +21 -0
  90. signalflow_trading-0.2.1.dist-info/top_level.txt +1 -0
signalflow/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ import signalflow.core as core
2
+ import signalflow.data as data
3
+ import signalflow.detector as detector
4
+ import signalflow.feature as feature
5
+ import signalflow.target as target
6
+ import signalflow.strategy as strategy
7
+ import signalflow.utils as utils
8
+ import signalflow.validator as validator
9
+
10
+
11
+
12
+ __all__ = [
13
+ "core",
14
+ "data",
15
+ "detector",
16
+ "feature",
17
+ "target",
18
+ "strategy"
19
+ "utils",
20
+ "validator",
21
+ ]
File without changes
@@ -0,0 +1,46 @@
1
+ from signalflow.core.containers import (
2
+ RawData,
3
+ Signals,
4
+ RawDataView,
5
+ Position,
6
+ Trade,
7
+ Portfolio,
8
+ StrategyState,
9
+ Order,
10
+ OrderFill
11
+ )
12
+ from signalflow.core.enums import (
13
+ SignalType,
14
+ PositionType,
15
+ SfComponentType,
16
+ DataFrameType,
17
+ RawDataType
18
+ )
19
+ from signalflow.core.decorators import sf_component
20
+ from signalflow.core.registry import default_registry, SignalFlowRegistry
21
+ from signalflow.core.signal_transforms import SignalsTransform
22
+ from signalflow.core.rolling_aggregator import RollingAggregator
23
+ from signalflow.core.base_mixin import SfTorchModuleMixin
24
+
25
+ __all__ = [
26
+ "RawData",
27
+ "Signals",
28
+ "RawDataView",
29
+ "Position",
30
+ "Trade",
31
+ "Order",
32
+ "OrderFill",
33
+ "Portfolio",
34
+ "StrategyState",
35
+ "SignalType",
36
+ "PositionType",
37
+ "SfComponentType",
38
+ "DataFrameType",
39
+ "RawDataType",
40
+ "sf_component",
41
+ "default_registry",
42
+ "SignalFlowRegistry",
43
+ "RollingAggregator",
44
+ "SignalsTransform",
45
+ "SfTorchModuleMixin",
46
+ ]
@@ -0,0 +1,232 @@
1
+ from abc import ABC, abstractmethod
2
+ import optuna
3
+ from signalflow.core.enums import SfComponentType
4
+ from typing import Literal
5
+
6
+
7
+ class SfTorchModuleMixin(ABC):
8
+ """Mixin for all SignalFlow neural network modules.
9
+
10
+ Provides standardized interface for PyTorch modules used in SignalFlow,
11
+ including default parameters and hyperparameter tuning via Optuna.
12
+
13
+ All neural network modules (detectors, validators, etc.) should inherit
14
+ from this mixin to ensure consistent configuration and tuning interfaces.
15
+
16
+ Key features:
17
+ - Automatic component type registration
18
+ - Standardized parameter interface
19
+ - Built-in Optuna integration for hyperparameter tuning
20
+ - Size-based architecture variants (small, medium, large)
21
+
22
+ Attributes:
23
+ component_type (SfComponentType): Always set to TORCH_MODULE for registry.
24
+
25
+ Example:
26
+ ```python
27
+ import torch
28
+ import torch.nn as nn
29
+ import optuna
30
+ from signalflow.core import SfTorchModuleMixin
31
+
32
+ class MyLSTMDetector(nn.Module, SfTorchModuleMixin):
33
+ def __init__(self, input_size: int, hidden_size: int, num_layers: int):
34
+ super().__init__()
35
+ self.lstm = nn.LSTM(input_size, hidden_size, num_layers)
36
+ self.fc = nn.Linear(hidden_size, 3) # 3 classes
37
+
38
+ def forward(self, x):
39
+ out, _ = self.lstm(x)
40
+ return self.fc(out[:, -1, :])
41
+
42
+ @classmethod
43
+ def default_params(cls) -> dict:
44
+ return {
45
+ "input_size": 10,
46
+ "hidden_size": 64,
47
+ "num_layers": 2
48
+ }
49
+
50
+ @classmethod
51
+ def tune(cls, trial: optuna.Trial, model_size: str = "small") -> dict:
52
+ if model_size == "small":
53
+ hidden_range = (32, 64)
54
+ layers_range = (1, 2)
55
+ elif model_size == "medium":
56
+ hidden_range = (64, 128)
57
+ layers_range = (2, 3)
58
+ else: # large
59
+ hidden_range = (128, 256)
60
+ layers_range = (3, 4)
61
+
62
+ return {
63
+ "input_size": 10,
64
+ "hidden_size": trial.suggest_int("hidden_size", *hidden_range),
65
+ "num_layers": trial.suggest_int("num_layers", *layers_range)
66
+ }
67
+
68
+ # Use default params
69
+ model = MyLSTMDetector(**MyLSTMDetector.default_params())
70
+
71
+ # Hyperparameter tuning
72
+ study = optuna.create_study()
73
+
74
+ def objective(trial):
75
+ params = MyLSTMDetector.tune(trial, model_size="medium")
76
+ model = MyLSTMDetector(**params)
77
+ # ... train and evaluate model ...
78
+ return validation_loss
79
+
80
+ study.optimize(objective, n_trials=100)
81
+ ```
82
+
83
+ Note:
84
+ Classes inheriting this mixin must implement both abstract methods.
85
+ The component_type is automatically set for registry integration.
86
+
87
+ See Also:
88
+ sf_component: Decorator for registering modules in the registry.
89
+ SfComponentType: Enum of all component types including TORCH_MODULE.
90
+ """
91
+
92
+ component_type: SfComponentType = SfComponentType.TORCH_MODULE
93
+
94
+ @classmethod
95
+ @abstractmethod
96
+ def default_params(cls) -> dict:
97
+ """Get default parameters for module instantiation.
98
+
99
+ Provides sensible defaults for quick prototyping and baseline comparisons.
100
+ These parameters should work reasonably well out-of-the-box.
101
+
102
+ Returns:
103
+ dict: Dictionary of parameter names and default values.
104
+ Keys match constructor argument names.
105
+
106
+ Example:
107
+ ```python
108
+ class MyTransformer(nn.Module, SfTorchModuleMixin):
109
+ def __init__(self, d_model: int, nhead: int, num_layers: int):
110
+ super().__init__()
111
+ # ... initialization ...
112
+
113
+ @classmethod
114
+ def default_params(cls) -> dict:
115
+ return {
116
+ "d_model": 128,
117
+ "nhead": 8,
118
+ "num_layers": 3
119
+ }
120
+
121
+ # Instantiate with defaults
122
+ model = MyTransformer(**MyTransformer.default_params())
123
+
124
+ # Override specific params
125
+ params = MyTransformer.default_params()
126
+ params["d_model"] = 256
127
+ model = MyTransformer(**params)
128
+ ```
129
+
130
+ Note:
131
+ Should be comprehensive - include all constructor parameters.
132
+ Consider computational constraints when setting defaults.
133
+ """
134
+ ...
135
+
136
+ @classmethod
137
+ @abstractmethod
138
+ def tune(cls, trial: optuna.Trial, model_size: Literal["small", "medium", "large"] = "small") -> dict:
139
+ """Define Optuna hyperparameter search space.
140
+
141
+ Provides size-based architecture variants for different computational budgets:
142
+ - small: Fast training, limited capacity
143
+ - medium: Balanced performance/speed
144
+ - large: Maximum capacity, slower training
145
+
146
+ Args:
147
+ trial (optuna.Trial): Optuna trial object for suggesting hyperparameters.
148
+ model_size (Literal["small", "medium", "large"]): Architecture size variant.
149
+ Default: "small".
150
+
151
+ Returns:
152
+ dict: Dictionary of hyperparameters sampled from search space.
153
+ Keys match constructor argument names.
154
+
155
+ Example:
156
+ ```python
157
+ class MyRNN(nn.Module, SfTorchModuleMixin):
158
+ def __init__(self, input_size: int, hidden_size: int,
159
+ num_layers: int, dropout: float):
160
+ super().__init__()
161
+ # ... initialization ...
162
+
163
+ @classmethod
164
+ def default_params(cls) -> dict:
165
+ return {
166
+ "input_size": 20,
167
+ "hidden_size": 64,
168
+ "num_layers": 2,
169
+ "dropout": 0.1
170
+ }
171
+
172
+ @classmethod
173
+ def tune(cls, trial: optuna.Trial, model_size: str = "small") -> dict:
174
+ # Size-based ranges
175
+ size_config = {
176
+ "small": {
177
+ "hidden": (32, 64),
178
+ "layers": (1, 2)
179
+ },
180
+ "medium": {
181
+ "hidden": (64, 128),
182
+ "layers": (2, 3)
183
+ },
184
+ "large": {
185
+ "hidden": (128, 256),
186
+ "layers": (3, 5)
187
+ }
188
+ }
189
+
190
+ config = size_config[model_size]
191
+
192
+ return {
193
+ "input_size": 20,
194
+ "hidden_size": trial.suggest_int(
195
+ "hidden_size",
196
+ *config["hidden"]
197
+ ),
198
+ "num_layers": trial.suggest_int(
199
+ "num_layers",
200
+ *config["layers"]
201
+ ),
202
+ "dropout": trial.suggest_float("dropout", 0.0, 0.5)
203
+ }
204
+
205
+ # Run hyperparameter search
206
+ import optuna
207
+
208
+ def objective(trial):
209
+ params = MyRNN.tune(trial, model_size="medium")
210
+ model = MyRNN(**params)
211
+
212
+ # Train model
213
+ trainer = Trainer(model, train_data, val_data)
214
+ val_loss = trainer.train()
215
+
216
+ return val_loss
217
+
218
+ study = optuna.create_study(direction="minimize")
219
+ study.optimize(objective, n_trials=50)
220
+
221
+ # Get best params
222
+ best_params = study.best_params
223
+ best_model = MyRNN(**MyRNN.tune(study.best_trial, "medium"))
224
+ ```
225
+
226
+ Note:
227
+ Search space should be tailored to model_size.
228
+ Use trial.suggest_* methods (int, float, categorical).
229
+ Return dict must be compatible with constructor.
230
+ Consider pruning for early stopping of poor trials.
231
+ """
232
+ ...
@@ -0,0 +1,21 @@
1
+ from signalflow.core.containers.raw_data import RawData
2
+ from signalflow.core.containers.raw_data_view import RawDataView
3
+ from signalflow.core.containers.signals import Signals
4
+ from signalflow.core.containers.position import Position
5
+ from signalflow.core.containers.trade import Trade
6
+ from signalflow.core.containers.portfolio import Portfolio
7
+ from signalflow.core.containers.strategy_state import StrategyState
8
+ from signalflow.core.containers.order import Order, OrderFill
9
+
10
+
11
+ __all__ = [
12
+ "RawData",
13
+ "RawDataView",
14
+ "Signals",
15
+ "Position",
16
+ "Trade",
17
+ "Portfolio",
18
+ "StrategyState",
19
+ "Order",
20
+ "OrderFill",
21
+ ]
@@ -0,0 +1,216 @@
1
+ """Order and OrderFill containers for strategy execution."""
2
+ from __future__ import annotations
3
+ import uuid
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from typing import Any, Literal
7
+
8
+ OrderSide = Literal['BUY', 'SELL']
9
+ OrderType = Literal['MARKET', 'LIMIT']
10
+ OrderStatus = Literal['NEW', 'FILLED', 'PARTIALLY_FILLED', 'CANCELLED', 'REJECTED']
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class Order:
15
+ """Trading order intent (mutable).
16
+
17
+ Represents the intent to trade. Orders are instructions that may or may not
18
+ be filled by the executor.
19
+
20
+ Order lifecycle:
21
+ NEW → PARTIALLY_FILLED → FILLED
22
+ → CANCELLED
23
+ → REJECTED
24
+
25
+ Attributes:
26
+ id (str): Unique order identifier.
27
+ pair (str): Trading pair (e.g. "BTCUSDT").
28
+ side (OrderSide): Order side - "BUY" or "SELL".
29
+ order_type (OrderType): Order type - "MARKET" or "LIMIT".
30
+ qty (float): Order quantity (always positive).
31
+ price (float | None): Limit price (None for MARKET orders).
32
+ created_at (datetime | None): Order creation timestamp.
33
+ status (OrderStatus): Current order status.
34
+ position_id (str | None): ID of position this order affects.
35
+ signal_strength (float): Strength of signal triggering order (0-1).
36
+ meta (dict[str, Any]): Additional metadata (e.g., signal info, exit reason).
37
+
38
+ Example:
39
+ ```python
40
+ from signalflow.core import Order
41
+
42
+ # Market buy order
43
+ market_order = Order(
44
+ pair="BTCUSDT",
45
+ side="BUY",
46
+ order_type="MARKET",
47
+ qty=0.5,
48
+ position_id="pos_123",
49
+ signal_strength=0.85,
50
+ meta={"type": "entry", "signal": "sma_cross"}
51
+ )
52
+
53
+ # Limit sell order
54
+ limit_order = Order(
55
+ pair="BTCUSDT",
56
+ side="SELL",
57
+ order_type="LIMIT",
58
+ qty=0.5,
59
+ price=46000.0,
60
+ position_id="pos_123",
61
+ meta={"type": "exit", "reason": "take_profit"}
62
+ )
63
+
64
+ # Check order properties
65
+ assert market_order.is_buy
66
+ assert market_order.is_market
67
+ assert limit_order.is_sell
68
+ ```
69
+
70
+ Note:
71
+ Orders are mutable to allow status updates.
72
+ Not all orders result in fills (e.g., insufficient liquidity, rejected).
73
+ """
74
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
75
+ pair: str = ''
76
+ side: OrderSide = 'BUY'
77
+ order_type: OrderType = 'MARKET'
78
+ qty: float = 0.0
79
+ price: float | None = None
80
+ created_at: datetime | None = None
81
+ status: OrderStatus = 'NEW'
82
+ position_id: str | None = None
83
+ signal_strength: float = 1.0
84
+ meta: dict[str, Any] = field(default_factory=dict)
85
+
86
+ @property
87
+ def is_buy(self) -> bool:
88
+ """Check if order is a buy order.
89
+
90
+ Returns:
91
+ bool: True if side is "BUY".
92
+
93
+ Example:
94
+ ```python
95
+ order = Order(side="BUY")
96
+ assert order.is_buy
97
+ ```
98
+ """
99
+ return self.side == 'BUY'
100
+
101
+ @property
102
+ def is_sell(self) -> bool:
103
+ """Check if order is a sell order.
104
+
105
+ Returns:
106
+ bool: True if side is "SELL".
107
+
108
+ Example:
109
+ ```python
110
+ order = Order(side="SELL")
111
+ assert order.is_sell
112
+ ```
113
+ """
114
+ return self.side == 'SELL'
115
+
116
+ @property
117
+ def is_market(self) -> bool:
118
+ """Check if order is a market order.
119
+
120
+ Returns:
121
+ bool: True if order_type is "MARKET".
122
+
123
+ Example:
124
+ ```python
125
+ order = Order(order_type="MARKET")
126
+ assert order.is_market
127
+ ```
128
+ """
129
+ return self.order_type == 'MARKET'
130
+
131
+
132
+ @dataclass(frozen=True, slots=True)
133
+ class OrderFill:
134
+ """Order execution result (immutable).
135
+
136
+ Represents the actual execution of an order. Maps directly to trade(s).
137
+ OrderFill is the bridge between order intent and executed trades.
138
+
139
+ Attributes:
140
+ id (str): Unique fill identifier.
141
+ order_id (str): ID of the order that was filled.
142
+ pair (str): Trading pair.
143
+ side (OrderSide): Fill side - "BUY" or "SELL".
144
+ ts (datetime | None): Fill timestamp.
145
+ price (float): Execution price.
146
+ qty (float): Filled quantity (may differ from order qty for partial fills).
147
+ fee (float): Transaction fee for this fill.
148
+ position_id (str | None): ID of the position affected.
149
+ meta (dict[str, Any]): Additional fill metadata.
150
+
151
+ Example:
152
+ ```python
153
+ from signalflow.core import OrderFill
154
+ from datetime import datetime
155
+
156
+ # Complete fill
157
+ fill = OrderFill(
158
+ order_id="order_123",
159
+ pair="BTCUSDT",
160
+ side="BUY",
161
+ ts=datetime.now(),
162
+ price=45000.0,
163
+ qty=0.5,
164
+ fee=22.5,
165
+ position_id="pos_123"
166
+ )
167
+
168
+ # Check fill details
169
+ print(f"Notional: ${fill.notional:.2f}")
170
+ print(f"Fee: ${fill.fee:.2f}")
171
+
172
+ # Partial fill
173
+ partial_fill = OrderFill(
174
+ order_id="order_456",
175
+ pair="BTCUSDT",
176
+ side="BUY",
177
+ ts=datetime.now(),
178
+ price=45100.0,
179
+ qty=0.3, # Order was for 0.5
180
+ fee=13.53,
181
+ position_id="pos_123",
182
+ meta={"status": "partial", "remaining": 0.2}
183
+ )
184
+ ```
185
+
186
+ Note:
187
+ OrderFill.qty may be less than Order.qty (partial fills).
188
+ Always check fills to update accounting correctly.
189
+ """
190
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
191
+ order_id: str = ''
192
+ pair: str = ''
193
+ side: OrderSide = 'BUY'
194
+ ts: datetime | None = None
195
+ price: float = 0.0
196
+ qty: float = 0.0
197
+ fee: float = 0.0
198
+ position_id: str | None = None
199
+ meta: dict[str, Any] = field(default_factory=dict)
200
+
201
+ @property
202
+ def notional(self) -> float:
203
+ """Calculate notional value of the fill.
204
+
205
+ Notional = price * qty
206
+
207
+ Returns:
208
+ float: Notional value in currency units.
209
+
210
+ Example:
211
+ ```python
212
+ fill = OrderFill(price=45000.0, qty=0.5)
213
+ assert fill.notional == 22500.0 # 45000 * 0.5
214
+ ```
215
+ """
216
+ return self.price * self.qty