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.
- signalflow/__init__.py +21 -0
- signalflow/analytics/__init__.py +0 -0
- signalflow/core/__init__.py +46 -0
- signalflow/core/base_mixin.py +232 -0
- signalflow/core/containers/__init__.py +21 -0
- signalflow/core/containers/order.py +216 -0
- signalflow/core/containers/portfolio.py +211 -0
- signalflow/core/containers/position.py +296 -0
- signalflow/core/containers/raw_data.py +167 -0
- signalflow/core/containers/raw_data_view.py +169 -0
- signalflow/core/containers/signals.py +198 -0
- signalflow/core/containers/strategy_state.py +147 -0
- signalflow/core/containers/trade.py +112 -0
- signalflow/core/decorators.py +103 -0
- signalflow/core/enums.py +270 -0
- signalflow/core/registry.py +322 -0
- signalflow/core/rolling_aggregator.py +362 -0
- signalflow/core/signal_transforms/__init__.py +5 -0
- signalflow/core/signal_transforms/base_signal_transform.py +186 -0
- signalflow/data/__init__.py +11 -0
- signalflow/data/raw_data_factory.py +225 -0
- signalflow/data/raw_store/__init__.py +7 -0
- signalflow/data/raw_store/base.py +271 -0
- signalflow/data/raw_store/duckdb_stores.py +696 -0
- signalflow/data/source/__init__.py +10 -0
- signalflow/data/source/base.py +300 -0
- signalflow/data/source/binance.py +442 -0
- signalflow/data/strategy_store/__init__.py +8 -0
- signalflow/data/strategy_store/base.py +278 -0
- signalflow/data/strategy_store/duckdb.py +409 -0
- signalflow/data/strategy_store/schema.py +36 -0
- signalflow/detector/__init__.py +7 -0
- signalflow/detector/adapter/__init__.py +5 -0
- signalflow/detector/adapter/pandas_detector.py +46 -0
- signalflow/detector/base.py +390 -0
- signalflow/detector/sma_cross.py +105 -0
- signalflow/feature/__init__.py +16 -0
- signalflow/feature/adapter/__init__.py +5 -0
- signalflow/feature/adapter/pandas_feature_extractor.py +54 -0
- signalflow/feature/base.py +330 -0
- signalflow/feature/feature_set.py +286 -0
- signalflow/feature/oscillator/__init__.py +5 -0
- signalflow/feature/oscillator/rsi_extractor.py +42 -0
- signalflow/feature/pandasta/__init__.py +10 -0
- signalflow/feature/pandasta/pandas_ta_extractor.py +141 -0
- signalflow/feature/pandasta/top_pandasta_extractors.py +64 -0
- signalflow/feature/smoother/__init__.py +5 -0
- signalflow/feature/smoother/sma_extractor.py +46 -0
- signalflow/strategy/__init__.py +9 -0
- signalflow/strategy/broker/__init__.py +15 -0
- signalflow/strategy/broker/backtest.py +172 -0
- signalflow/strategy/broker/base.py +186 -0
- signalflow/strategy/broker/executor/__init__.py +9 -0
- signalflow/strategy/broker/executor/base.py +35 -0
- signalflow/strategy/broker/executor/binance_spot.py +12 -0
- signalflow/strategy/broker/executor/virtual_spot.py +81 -0
- signalflow/strategy/broker/realtime_spot.py +12 -0
- signalflow/strategy/component/__init__.py +9 -0
- signalflow/strategy/component/base.py +65 -0
- signalflow/strategy/component/entry/__init__.py +7 -0
- signalflow/strategy/component/entry/fixed_size.py +57 -0
- signalflow/strategy/component/entry/signal.py +127 -0
- signalflow/strategy/component/exit/__init__.py +5 -0
- signalflow/strategy/component/exit/time_based.py +47 -0
- signalflow/strategy/component/exit/tp_sl.py +80 -0
- signalflow/strategy/component/metric/__init__.py +8 -0
- signalflow/strategy/component/metric/main_metrics.py +181 -0
- signalflow/strategy/runner/__init__.py +8 -0
- signalflow/strategy/runner/backtest_runner.py +208 -0
- signalflow/strategy/runner/base.py +19 -0
- signalflow/strategy/runner/optimized_backtest_runner.py +178 -0
- signalflow/strategy/runner/realtime_runner.py +0 -0
- signalflow/target/__init__.py +14 -0
- signalflow/target/adapter/__init__.py +5 -0
- signalflow/target/adapter/pandas_labeler.py +45 -0
- signalflow/target/base.py +409 -0
- signalflow/target/fixed_horizon_labeler.py +93 -0
- signalflow/target/static_triple_barrier.py +162 -0
- signalflow/target/triple_barrier.py +188 -0
- signalflow/utils/__init__.py +7 -0
- signalflow/utils/import_utils.py +11 -0
- signalflow/utils/tune_utils.py +19 -0
- signalflow/validator/__init__.py +6 -0
- signalflow/validator/base.py +139 -0
- signalflow/validator/sklearn_validator.py +527 -0
- signalflow_trading-0.2.1.dist-info/METADATA +149 -0
- signalflow_trading-0.2.1.dist-info/RECORD +90 -0
- signalflow_trading-0.2.1.dist-info/WHEEL +5 -0
- signalflow_trading-0.2.1.dist-info/licenses/LICENSE +21 -0
- signalflow_trading-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, ClassVar
|
|
5
|
+
from signalflow.core import SfComponentType
|
|
6
|
+
import polars as pl
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RawDataSource(ABC):
|
|
10
|
+
"""Abstract base class for raw data sources.
|
|
11
|
+
|
|
12
|
+
Defines the interface for data sources that provide market data
|
|
13
|
+
(exchanges, APIs, files, etc.). Sources are passive - they define
|
|
14
|
+
where data comes from but don't handle downloading.
|
|
15
|
+
|
|
16
|
+
RawDataSource is typically used in combination with RawDataLoader,
|
|
17
|
+
which handles the actual data retrieval and storage logic.
|
|
18
|
+
|
|
19
|
+
Key responsibilities:
|
|
20
|
+
- Define connection parameters (API keys, endpoints, etc.)
|
|
21
|
+
- Provide authentication credentials
|
|
22
|
+
- Specify data source configuration
|
|
23
|
+
|
|
24
|
+
Common implementations:
|
|
25
|
+
- Exchange APIs (Binance, Coinbase, etc.)
|
|
26
|
+
- Data providers (CryptoCompare, CoinGecko, etc.)
|
|
27
|
+
- File sources (CSV, Parquet, etc.)
|
|
28
|
+
- Database connections
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
component_type (ClassVar[SfComponentType]): Always RAW_DATA_SOURCE for registry.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
from signalflow.core import sf_component, SfComponentType
|
|
36
|
+
from dataclasses import dataclass
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
@sf_component(name="binance_spot")
|
|
40
|
+
class BinanceSpotSource(RawDataSource):
|
|
41
|
+
'''Binance Spot API source'''
|
|
42
|
+
api_key: str = ""
|
|
43
|
+
api_secret: str = ""
|
|
44
|
+
base_url: str = "https://api.binance.com"
|
|
45
|
+
|
|
46
|
+
def get_client(self):
|
|
47
|
+
'''Create authenticated client'''
|
|
48
|
+
from binance.client import Client
|
|
49
|
+
return Client(self.api_key, self.api_secret)
|
|
50
|
+
|
|
51
|
+
# Use with loader
|
|
52
|
+
source = BinanceSpotSource(
|
|
53
|
+
api_key="your_key",
|
|
54
|
+
api_secret="your_secret"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
loader = BinanceSpotLoader(
|
|
58
|
+
source=source,
|
|
59
|
+
store=store
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
```python
|
|
65
|
+
# File-based source
|
|
66
|
+
@dataclass
|
|
67
|
+
@sf_component(name="csv_source")
|
|
68
|
+
class CsvSource(RawDataSource):
|
|
69
|
+
'''CSV file source'''
|
|
70
|
+
file_path: Path
|
|
71
|
+
separator: str = ","
|
|
72
|
+
|
|
73
|
+
def read(self) -> pl.DataFrame:
|
|
74
|
+
return pl.read_csv(self.file_path, separator=self.separator)
|
|
75
|
+
|
|
76
|
+
# Database source
|
|
77
|
+
@dataclass
|
|
78
|
+
@sf_component(name="postgres_source")
|
|
79
|
+
class PostgresSource(RawDataSource):
|
|
80
|
+
'''PostgreSQL database source'''
|
|
81
|
+
host: str
|
|
82
|
+
port: int = 5432
|
|
83
|
+
database: str
|
|
84
|
+
user: str
|
|
85
|
+
password: str
|
|
86
|
+
|
|
87
|
+
def get_connection_string(self) -> str:
|
|
88
|
+
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Note:
|
|
92
|
+
Source classes are typically passive configuration containers.
|
|
93
|
+
Active data retrieval is handled by RawDataLoader implementations.
|
|
94
|
+
Use @sf_component decorator to register sources in the registry.
|
|
95
|
+
|
|
96
|
+
See Also:
|
|
97
|
+
RawDataLoader: Active component that uses sources to download data.
|
|
98
|
+
RawDataStore: Storage backend for persisting downloaded data.
|
|
99
|
+
"""
|
|
100
|
+
component_type: ClassVar[SfComponentType] = SfComponentType.RAW_DATA_SOURCE
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class RawDataLoader(ABC):
|
|
104
|
+
"""Abstract base class for raw data loaders.
|
|
105
|
+
|
|
106
|
+
Defines the interface for loading market data from sources and
|
|
107
|
+
storing it in persistent storage. Loaders orchestrate the data
|
|
108
|
+
pipeline: source → transformation → storage.
|
|
109
|
+
|
|
110
|
+
Key responsibilities:
|
|
111
|
+
- Download data from sources (download)
|
|
112
|
+
- Sync/update existing data (sync)
|
|
113
|
+
- Handle rate limiting and retries
|
|
114
|
+
- Transform data to canonical format
|
|
115
|
+
- Store data in persistent backend
|
|
116
|
+
|
|
117
|
+
Typical workflow:
|
|
118
|
+
1. Initial download: Fetch historical data
|
|
119
|
+
2. Incremental sync: Update with latest data
|
|
120
|
+
3. Gap filling: Detect and fill missing periods
|
|
121
|
+
4. Validation: Ensure data quality and completeness
|
|
122
|
+
|
|
123
|
+
Attributes:
|
|
124
|
+
component_type (ClassVar[SfComponentType]): Always RAW_DATA_LOADER for registry.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
```python
|
|
128
|
+
from signalflow.core import sf_component, SfComponentType
|
|
129
|
+
from signalflow.data.raw_store import DuckDbSpotStore
|
|
130
|
+
from datetime import datetime
|
|
131
|
+
|
|
132
|
+
@sf_component(name="binance_spot_loader")
|
|
133
|
+
class BinanceSpotLoader(RawDataLoader):
|
|
134
|
+
'''Loads Binance spot data'''
|
|
135
|
+
|
|
136
|
+
def __init__(self, source: BinanceSpotSource, store: DuckDbSpotStore):
|
|
137
|
+
self.source = source
|
|
138
|
+
self.store = store
|
|
139
|
+
self.client = source.get_client()
|
|
140
|
+
|
|
141
|
+
def download(self, pairs: list[str], start: datetime, end: datetime):
|
|
142
|
+
'''Download historical data'''
|
|
143
|
+
for pair in pairs:
|
|
144
|
+
klines = self.client.get_historical_klines(
|
|
145
|
+
symbol=pair,
|
|
146
|
+
interval="1m",
|
|
147
|
+
start_str=start.isoformat(),
|
|
148
|
+
end_str=end.isoformat()
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Transform to canonical format
|
|
152
|
+
formatted = self._format_klines(klines)
|
|
153
|
+
|
|
154
|
+
# Store
|
|
155
|
+
self.store.insert_klines(pair, formatted)
|
|
156
|
+
|
|
157
|
+
def sync(self, pairs: list[str]):
|
|
158
|
+
'''Sync latest data'''
|
|
159
|
+
for pair in pairs:
|
|
160
|
+
# Get last timestamp
|
|
161
|
+
_, max_ts = self.store.get_time_bounds(pair)
|
|
162
|
+
|
|
163
|
+
if max_ts:
|
|
164
|
+
# Fetch data from max_ts to now
|
|
165
|
+
self.download(
|
|
166
|
+
pairs=[pair],
|
|
167
|
+
start=max_ts,
|
|
168
|
+
end=datetime.now()
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _format_klines(self, klines):
|
|
172
|
+
'''Transform to canonical format'''
|
|
173
|
+
return [
|
|
174
|
+
{
|
|
175
|
+
"timestamp": datetime.fromtimestamp(k[0] / 1000),
|
|
176
|
+
"open": float(k[1]),
|
|
177
|
+
"high": float(k[2]),
|
|
178
|
+
"low": float(k[3]),
|
|
179
|
+
"close": float(k[4]),
|
|
180
|
+
"volume": float(k[5]),
|
|
181
|
+
"trades": int(k[8])
|
|
182
|
+
}
|
|
183
|
+
for k in klines
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# Usage
|
|
187
|
+
source = BinanceSpotSource(api_key="key", api_secret="secret")
|
|
188
|
+
store = DuckDbSpotStore(Path("data/binance.duckdb"))
|
|
189
|
+
loader = BinanceSpotLoader(source=source, store=store)
|
|
190
|
+
|
|
191
|
+
# Initial download
|
|
192
|
+
loader.download(
|
|
193
|
+
pairs=["BTCUSDT", "ETHUSDT"],
|
|
194
|
+
start=datetime(2024, 1, 1),
|
|
195
|
+
end=datetime(2024, 12, 31)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Daily sync
|
|
199
|
+
loader.sync(pairs=["BTCUSDT", "ETHUSDT"])
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Note:
|
|
203
|
+
Implementations should handle rate limiting and API errors gracefully.
|
|
204
|
+
sync() should be idempotent - safe to call multiple times.
|
|
205
|
+
Consider implementing gap detection and backfilling logic.
|
|
206
|
+
|
|
207
|
+
See Also:
|
|
208
|
+
RawDataSource: Defines where data comes from.
|
|
209
|
+
RawDataStore: Defines where data is stored.
|
|
210
|
+
RawDataFactory: Creates RawData from stored data.
|
|
211
|
+
"""
|
|
212
|
+
component_type: ClassVar[SfComponentType] = SfComponentType.RAW_DATA_LOADER
|
|
213
|
+
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def download(self, **kwargs):
|
|
216
|
+
"""Download historical data from source to storage.
|
|
217
|
+
|
|
218
|
+
Initial data acquisition for a date range. Typically used for:
|
|
219
|
+
- First-time setup with historical data
|
|
220
|
+
- Backfilling large time periods
|
|
221
|
+
- Bulk data imports
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
**kwargs: Implementation-specific parameters. Common parameters:
|
|
225
|
+
- pairs (list[str]): Trading pairs to download
|
|
226
|
+
- start (datetime): Start datetime
|
|
227
|
+
- end (datetime): End datetime
|
|
228
|
+
- timeframe (str): Candlestick timeframe (e.g., "1m", "1h")
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
```python
|
|
232
|
+
# Download historical data
|
|
233
|
+
loader.download(
|
|
234
|
+
pairs=["BTCUSDT", "ETHUSDT"],
|
|
235
|
+
start=datetime(2024, 1, 1),
|
|
236
|
+
end=datetime(2024, 12, 31),
|
|
237
|
+
timeframe="1m"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Download with progress tracking
|
|
241
|
+
loader.download(
|
|
242
|
+
pairs=["BTCUSDT"],
|
|
243
|
+
start=datetime(2024, 1, 1),
|
|
244
|
+
end=datetime(2024, 1, 31),
|
|
245
|
+
batch_size=1000,
|
|
246
|
+
show_progress=True
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Note:
|
|
251
|
+
Should handle API rate limits with retries/delays.
|
|
252
|
+
Consider implementing chunking for large date ranges.
|
|
253
|
+
Validate data quality before storing.
|
|
254
|
+
"""
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
@abstractmethod
|
|
258
|
+
def sync(self, **kwargs):
|
|
259
|
+
"""Sync/update existing data with latest data.
|
|
260
|
+
|
|
261
|
+
Incremental update for keeping data current. Typically used for:
|
|
262
|
+
- Daily/hourly updates
|
|
263
|
+
- Real-time data synchronization
|
|
264
|
+
- Gap filling
|
|
265
|
+
|
|
266
|
+
Implementation should:
|
|
267
|
+
- Detect last available timestamp per pair
|
|
268
|
+
- Fetch missing data from last timestamp to now
|
|
269
|
+
- Handle overlapping data (upsert)
|
|
270
|
+
- Be idempotent (safe to run multiple times)
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
**kwargs: Implementation-specific parameters. Common parameters:
|
|
274
|
+
- pairs (list[str]): Trading pairs to sync
|
|
275
|
+
- force (bool): Force full resync instead of incremental
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
```python
|
|
279
|
+
# Incremental sync (fetch latest data)
|
|
280
|
+
loader.sync(pairs=["BTCUSDT", "ETHUSDT"])
|
|
281
|
+
|
|
282
|
+
# Force full resync
|
|
283
|
+
loader.sync(pairs=["BTCUSDT"], force=True)
|
|
284
|
+
|
|
285
|
+
# Scheduled sync (e.g., cron job)
|
|
286
|
+
import schedule
|
|
287
|
+
|
|
288
|
+
def daily_sync():
|
|
289
|
+
loader.sync(pairs=["BTCUSDT", "ETHUSDT"])
|
|
290
|
+
print(f"Synced at {datetime.now()}")
|
|
291
|
+
|
|
292
|
+
schedule.every().day.at("00:00").do(daily_sync)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Note:
|
|
296
|
+
Should be idempotent - running multiple times is safe.
|
|
297
|
+
Consider checking for gaps and backfilling if needed.
|
|
298
|
+
Log sync status for monitoring.
|
|
299
|
+
"""
|
|
300
|
+
pass
|