investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__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.
- investing_algorithm_framework/__init__.py +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import polars as pl
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import List, Tuple, Optional, Dict, Any
|
|
7
|
+
|
|
8
|
+
from investing_algorithm_framework.domain import DataProvider, \
|
|
9
|
+
OperationalException, ImproperlyConfigured, DataSource, DataType, \
|
|
10
|
+
BacktestDateRange, tqdm, convert_polars_to_pandas, TimeFrame
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DataProviderIndex:
|
|
16
|
+
"""
|
|
17
|
+
Efficient lookup for data providers in O(1) time.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
data_providers (List[DataProvider]): List of data providers
|
|
21
|
+
data_providers_lookup (dict): Dictionary to store the lookup
|
|
22
|
+
for order executors based on market.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, data_providers=[]):
|
|
25
|
+
self.data_providers = data_providers
|
|
26
|
+
self.data_providers_lookup = defaultdict()
|
|
27
|
+
self.ohlcv_data_providers = defaultdict()
|
|
28
|
+
self.ohlcv_data_providers_no_market = defaultdict()
|
|
29
|
+
self.ohlcv_data_providers_with_timeframe = defaultdict()
|
|
30
|
+
self.ticker_data_providers = defaultdict()
|
|
31
|
+
|
|
32
|
+
def add(self, data_provider: DataProvider):
|
|
33
|
+
"""
|
|
34
|
+
Add a data provider to the lookup.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data_provider (DataProvider): The data provider to be added.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
None
|
|
41
|
+
"""
|
|
42
|
+
self.data_providers.append(data_provider)
|
|
43
|
+
|
|
44
|
+
def register(self, data_source: DataSource) -> DataProvider:
|
|
45
|
+
"""
|
|
46
|
+
Register a data source in the DataProvider Index.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
This method will go over all data providers and select the
|
|
50
|
+
best matching data provider for the data source.
|
|
51
|
+
|
|
52
|
+
If multiple data providers are found for the data source,
|
|
53
|
+
it will sort them by priority and pick the best one.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
data_source (DataSource): The data source to register the
|
|
57
|
+
data provider for.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
None
|
|
61
|
+
"""
|
|
62
|
+
matches = []
|
|
63
|
+
|
|
64
|
+
for data_provider in self.data_providers:
|
|
65
|
+
|
|
66
|
+
if data_provider.has_data(data_source):
|
|
67
|
+
matches.append(data_provider)
|
|
68
|
+
|
|
69
|
+
if len(matches) == 0:
|
|
70
|
+
params = data_source.to_dict()
|
|
71
|
+
raise ImproperlyConfigured(
|
|
72
|
+
f"No data provider found for given parameters: {params}."
|
|
73
|
+
f" Please make sure that you have registered a data provider "
|
|
74
|
+
f"provider for the market and symbol you are trying to use"
|
|
75
|
+
)
|
|
76
|
+
# Sort by priority and pick the best one
|
|
77
|
+
best_provider = sorted(matches, key=lambda x: x.priority)[0]
|
|
78
|
+
best_provider = best_provider.copy(data_source)
|
|
79
|
+
# Copy the data provider and set the attributes
|
|
80
|
+
self.data_providers_lookup[data_source] = best_provider
|
|
81
|
+
|
|
82
|
+
symbol = data_source.symbol
|
|
83
|
+
market = data_source.market
|
|
84
|
+
time_frame = data_source.time_frame
|
|
85
|
+
|
|
86
|
+
if DataType.OHLCV.equals(data_source.data_type):
|
|
87
|
+
if symbol not in self.ohlcv_data_providers:
|
|
88
|
+
self.ohlcv_data_providers[(symbol, market)] = best_provider
|
|
89
|
+
self.ohlcv_data_providers_no_market[symbol] = best_provider
|
|
90
|
+
self.ohlcv_data_providers_with_timeframe[
|
|
91
|
+
(symbol, market, time_frame)
|
|
92
|
+
] = best_provider
|
|
93
|
+
else:
|
|
94
|
+
try:
|
|
95
|
+
# If the symbol already exists, we can update the provider
|
|
96
|
+
# has a more granular timeframe
|
|
97
|
+
existing_provider = self.ohlcv_data_providers[
|
|
98
|
+
(symbol, market)
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
if existing_provider.time_frame > best_provider.time_frame:
|
|
102
|
+
self.ohlcv_data_providers[(symbol, market)] =\
|
|
103
|
+
best_provider
|
|
104
|
+
|
|
105
|
+
existing_provider = self.ohlcv_data_providers_no_market[
|
|
106
|
+
symbol
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
if existing_provider.time_frame > best_provider.time_frame:
|
|
110
|
+
self.ohlcv_data_providers_no_market[symbol] =\
|
|
111
|
+
best_provider
|
|
112
|
+
|
|
113
|
+
time_frame_key = (symbol, market, time_frame)
|
|
114
|
+
self.ohlcv_data_providers_with_timeframe[
|
|
115
|
+
time_frame_key
|
|
116
|
+
] = best_provider
|
|
117
|
+
|
|
118
|
+
except Exception:
|
|
119
|
+
# If the existing provider does not have a time_frame
|
|
120
|
+
# attribute, we can safely ignore this
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
elif DataType.TICKER.equals(data_source.data_type):
|
|
124
|
+
if symbol not in self.ticker_data_providers:
|
|
125
|
+
self.ticker_data_providers[symbol] = best_provider
|
|
126
|
+
|
|
127
|
+
return best_provider
|
|
128
|
+
|
|
129
|
+
def register_backtest_data_source(
|
|
130
|
+
self,
|
|
131
|
+
data_source: DataSource,
|
|
132
|
+
backtest_date_range: BacktestDateRange
|
|
133
|
+
) -> DataProvider:
|
|
134
|
+
"""
|
|
135
|
+
Register a backtest data source for a given market and symbol.
|
|
136
|
+
|
|
137
|
+
This method will also check if the data provider supports
|
|
138
|
+
the market. If no data provider is found for the market and symbol,
|
|
139
|
+
it will raise an ImproperlyConfigured exception.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
data_source (DataSource): The data source to register the
|
|
143
|
+
backtest data provider for.
|
|
144
|
+
backtest_date_range (BacktestDateRange): The date range for the
|
|
145
|
+
backtest data provider.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
DataProvider: The registered data provider.
|
|
149
|
+
"""
|
|
150
|
+
matches = []
|
|
151
|
+
|
|
152
|
+
for data_provider in self.data_providers:
|
|
153
|
+
|
|
154
|
+
if data_provider.has_data(
|
|
155
|
+
data_source,
|
|
156
|
+
start_date=backtest_date_range.start_date,
|
|
157
|
+
end_date=backtest_date_range.end_date
|
|
158
|
+
):
|
|
159
|
+
matches.append(data_provider)
|
|
160
|
+
|
|
161
|
+
if len(matches) == 0:
|
|
162
|
+
params = data_source.to_dict()
|
|
163
|
+
raise ImproperlyConfigured(
|
|
164
|
+
f"No data provider found for given parameters: {params}."
|
|
165
|
+
f" Please make sure that you have registered a data provider "
|
|
166
|
+
f"provider for the defined datasource. If you are using a "
|
|
167
|
+
"custom data provider, make sure it has a "
|
|
168
|
+
"data_provider_identifier set"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Sort by priority and pick the best one (lowest priority first)
|
|
172
|
+
best_provider = sorted(matches, key=lambda x: x.priority)[0]
|
|
173
|
+
best_provider = best_provider.copy(data_source)
|
|
174
|
+
self.data_providers_lookup[data_source] = best_provider
|
|
175
|
+
|
|
176
|
+
symbol = data_source.symbol
|
|
177
|
+
market = data_source.market
|
|
178
|
+
time_frame = data_source.time_frame
|
|
179
|
+
|
|
180
|
+
if DataType.OHLCV.equals(data_source.data_type):
|
|
181
|
+
|
|
182
|
+
if symbol not in self.ohlcv_data_providers:
|
|
183
|
+
self.ohlcv_data_providers[(symbol, market)] = best_provider
|
|
184
|
+
self.ohlcv_data_providers_no_market[symbol] = best_provider
|
|
185
|
+
self.ohlcv_data_providers_with_timeframe[
|
|
186
|
+
(symbol, market, time_frame)
|
|
187
|
+
] = best_provider
|
|
188
|
+
else:
|
|
189
|
+
try:
|
|
190
|
+
# If the symbol already exists, we can update the provider
|
|
191
|
+
# has a more granular timeframe
|
|
192
|
+
existing_provider = self.ohlcv_data_providers[
|
|
193
|
+
(symbol.upper(), market.upper())
|
|
194
|
+
]
|
|
195
|
+
if existing_provider.time_frame > best_provider.time_frame:
|
|
196
|
+
self.ohlcv_data_providers[
|
|
197
|
+
(symbol.upper(), market.upper())
|
|
198
|
+
] =\
|
|
199
|
+
best_provider
|
|
200
|
+
|
|
201
|
+
existing_provider = self.ohlcv_data_providers_no_market[
|
|
202
|
+
symbol
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
if existing_provider.time_frame > best_provider.time_frame:
|
|
206
|
+
self.ohlcv_data_providers_no_market[symbol] = \
|
|
207
|
+
best_provider
|
|
208
|
+
|
|
209
|
+
time_frame_key = (symbol, market, data_source.time_frame)
|
|
210
|
+
self.ohlcv_data_providers_with_timeframe[
|
|
211
|
+
time_frame_key
|
|
212
|
+
] = best_provider
|
|
213
|
+
|
|
214
|
+
except Exception:
|
|
215
|
+
# If the existing provider does not have a time_frame
|
|
216
|
+
# attribute, we can safely ignore this
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
elif DataType.TICKER.equals(data_source.data_type):
|
|
220
|
+
if symbol not in self.ticker_data_providers:
|
|
221
|
+
self.ticker_data_providers[symbol] = best_provider
|
|
222
|
+
|
|
223
|
+
return best_provider
|
|
224
|
+
|
|
225
|
+
def get(self, data_source: DataSource) -> Optional[DataProvider]:
|
|
226
|
+
"""
|
|
227
|
+
Get the data provider for a given data source.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
data_source (DataSource): The data source to get the
|
|
231
|
+
data provider for.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
DataProvider: The data provider for the market and symbol,
|
|
235
|
+
"""
|
|
236
|
+
return self.data_providers_lookup.get(data_source, None)
|
|
237
|
+
|
|
238
|
+
def find(self, data_source: DataSource) -> Optional[DataProvider]:
|
|
239
|
+
"""
|
|
240
|
+
Find a data provider for a given data source.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
data_source (DataSource): The data source to find the
|
|
244
|
+
data provider for.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
DataProvider: The data provider for the market and symbol,
|
|
248
|
+
or None if no provider is found.
|
|
249
|
+
"""
|
|
250
|
+
return self.data_providers_lookup.get(data_source, None)
|
|
251
|
+
|
|
252
|
+
def get_all(self) -> List[Tuple[DataSource, DataProvider]]:
|
|
253
|
+
"""
|
|
254
|
+
Get all registered data providers with corresponding DataSource.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
List[Tuple[DataSource, DataProvider]]: A list of all
|
|
258
|
+
data providers with corresponding data sources.
|
|
259
|
+
"""
|
|
260
|
+
return [
|
|
261
|
+
(data_source, data_provider)
|
|
262
|
+
for data_source, data_provider
|
|
263
|
+
in self.data_providers_lookup.items()
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
def reset(self):
|
|
267
|
+
"""
|
|
268
|
+
Function to reset the order executor lookup table
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
None
|
|
272
|
+
"""
|
|
273
|
+
self.data_providers_lookup = defaultdict()
|
|
274
|
+
self.data_providers = []
|
|
275
|
+
|
|
276
|
+
def __len__(self):
|
|
277
|
+
"""
|
|
278
|
+
Returns the number of data providers in the index.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
int: The number of data providers.
|
|
282
|
+
"""
|
|
283
|
+
return len(self.data_providers_lookup)
|
|
284
|
+
|
|
285
|
+
def get_ohlcv_data_provider(
|
|
286
|
+
self,
|
|
287
|
+
symbol: str,
|
|
288
|
+
market: Optional[str] = None,
|
|
289
|
+
time_frame: Optional[str] = None
|
|
290
|
+
) -> Optional[DataProvider]:
|
|
291
|
+
"""
|
|
292
|
+
Get the OHLCV data provider for a given symbol and market.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
symbol (str): The symbol to get the data provider for.
|
|
296
|
+
market (Optional[str]): The market to get the data provider for.
|
|
297
|
+
time_frame (Optional[str]): The time frame to get the
|
|
298
|
+
data provider for.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
DataProvider: The OHLCV data provider for the symbol and market,
|
|
302
|
+
or None if no provider is found.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
if market is not None and time_frame is not None:
|
|
306
|
+
time_frame = TimeFrame.from_value(time_frame)
|
|
307
|
+
return self.ohlcv_data_providers_with_timeframe.get(
|
|
308
|
+
(symbol, market, time_frame), None
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if market is None:
|
|
312
|
+
# If no market is specified
|
|
313
|
+
return self.ohlcv_data_providers_no_market.get(symbol, None)
|
|
314
|
+
|
|
315
|
+
return self.ohlcv_data_providers.get((symbol, market), None)
|
|
316
|
+
|
|
317
|
+
def get_ticker_data_provider(
|
|
318
|
+
self, symbol: str, market: str
|
|
319
|
+
) -> Optional[DataProvider]:
|
|
320
|
+
"""
|
|
321
|
+
Get the ticker data provider for a given symbol and market.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
symbol (str): The symbol to get the data provider for.
|
|
325
|
+
market (str): The market to get the data provider for.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
DataProvider: The ticker data provider for the symbol and market,
|
|
329
|
+
or None if no provider is found.
|
|
330
|
+
"""
|
|
331
|
+
return self.ticker_data_providers.get(symbol, None)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class DataProviderService:
|
|
335
|
+
data_provider_index: DataProviderIndex = None
|
|
336
|
+
backtest_mode = False
|
|
337
|
+
|
|
338
|
+
def __init__(
|
|
339
|
+
self,
|
|
340
|
+
configuration_service,
|
|
341
|
+
market_credential_service,
|
|
342
|
+
default_data_providers: List[DataProvider] = [],
|
|
343
|
+
):
|
|
344
|
+
"""
|
|
345
|
+
Initialize the DataProviderService with a list of data providers.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
default_data_providers (List[DataProvider]): A list of default
|
|
349
|
+
data providers to use.
|
|
350
|
+
"""
|
|
351
|
+
self.default_data_providers = default_data_providers
|
|
352
|
+
self.data_provider_index = DataProviderIndex(default_data_providers)
|
|
353
|
+
self.configuration_service = configuration_service
|
|
354
|
+
self.market_credential_service = market_credential_service
|
|
355
|
+
|
|
356
|
+
def get(self, data_source: DataSource) -> Optional[DataProvider]:
|
|
357
|
+
"""
|
|
358
|
+
Get a registered data provider by its data source.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
data_source (DataSource): The data source to get the
|
|
362
|
+
data provider for.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Optional[DataProvider]: The registered data provider for
|
|
366
|
+
the data source, or None if not found.
|
|
367
|
+
"""
|
|
368
|
+
return self.data_provider_index.get(data_source)
|
|
369
|
+
|
|
370
|
+
def get_data(
|
|
371
|
+
self,
|
|
372
|
+
data_source: DataSource,
|
|
373
|
+
date: datetime = None,
|
|
374
|
+
start_date: datetime = None,
|
|
375
|
+
end_date: datetime = None,
|
|
376
|
+
):
|
|
377
|
+
"""
|
|
378
|
+
Function to get data from the data provider.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
data_source (DataSource): The data source specification that
|
|
382
|
+
matches a data provider.
|
|
383
|
+
date (datetime): The date to get data for.
|
|
384
|
+
start_date (datetime): The start date for the data.
|
|
385
|
+
end_date (datetime): The end date for the data.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
DataFrame: The data for the given symbol and market.
|
|
389
|
+
"""
|
|
390
|
+
data_provider = self.data_provider_index.get(data_source=data_source)
|
|
391
|
+
|
|
392
|
+
if data_provider is None:
|
|
393
|
+
dict_data = data_source.to_dict()
|
|
394
|
+
self._throw_no_data_provider_exception(dict_data)
|
|
395
|
+
|
|
396
|
+
if self.configuration_service is not None:
|
|
397
|
+
data_provider.config = self.configuration_service.get_config()
|
|
398
|
+
|
|
399
|
+
return data_provider.get_data(
|
|
400
|
+
date=date,
|
|
401
|
+
start_date=start_date,
|
|
402
|
+
end_date=end_date,
|
|
403
|
+
save=data_source.save,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
def get_ticker_data(self, symbol, market, date):
|
|
407
|
+
"""
|
|
408
|
+
Function to get a ticker for a given data source.
|
|
409
|
+
The data source should have its market and symbol defined.
|
|
410
|
+
All other attributes are ignored of the data source
|
|
411
|
+
"""
|
|
412
|
+
data_provider = self.data_provider_index.get_ticker_data_provider(
|
|
413
|
+
symbol=symbol, market=market
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if data_provider is None:
|
|
417
|
+
ohlcv_data_provider = self.data_provider_index.\
|
|
418
|
+
get_ohlcv_data_provider(
|
|
419
|
+
symbol=symbol, market=market
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if ohlcv_data_provider is None:
|
|
423
|
+
raise OperationalException(
|
|
424
|
+
"No ticker data provider found "
|
|
425
|
+
f"for symbol: {symbol} and market: {market} "
|
|
426
|
+
f"on date: {date}"
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
if self.backtest_mode:
|
|
430
|
+
data = ohlcv_data_provider.get_backtest_data(
|
|
431
|
+
backtest_index_date=date,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if isinstance(data, pd.DataFrame):
|
|
435
|
+
# Convert to Polars DataFrame for consistency
|
|
436
|
+
data.index.name = "Datetime"
|
|
437
|
+
data = data.reset_index()
|
|
438
|
+
data = pl.from_pandas(data)
|
|
439
|
+
entry = data[-1]
|
|
440
|
+
return {
|
|
441
|
+
"symbol": symbol,
|
|
442
|
+
"market": market,
|
|
443
|
+
"datetime": entry["Datetime"][0],
|
|
444
|
+
"open": entry["Open"][0],
|
|
445
|
+
"high": entry["High"][0],
|
|
446
|
+
"low": entry["Low"][0],
|
|
447
|
+
"close": entry["Close"][0],
|
|
448
|
+
"volume": entry["Close"][0],
|
|
449
|
+
"ask": entry["Close"][0],
|
|
450
|
+
"bid": entry["Close"][0],
|
|
451
|
+
}
|
|
452
|
+
else:
|
|
453
|
+
data = ohlcv_data_provider.get_data(
|
|
454
|
+
date=date,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
if isinstance(data, pd.DataFrame):
|
|
458
|
+
# Convert to Polars DataFrame for consistency
|
|
459
|
+
data.index.name = "Datetime"
|
|
460
|
+
data = data.reset_index()
|
|
461
|
+
data = pl.from_pandas(data)
|
|
462
|
+
|
|
463
|
+
entry = data[-1]
|
|
464
|
+
return {
|
|
465
|
+
"symbol": symbol,
|
|
466
|
+
"market": market,
|
|
467
|
+
"datetime": entry["Datetime"][0],
|
|
468
|
+
"open": entry["Open"][0],
|
|
469
|
+
"high": entry["High"][0],
|
|
470
|
+
"low": entry["Low"][0],
|
|
471
|
+
"close": entry["Close"][0],
|
|
472
|
+
"volume": entry["Close"][0],
|
|
473
|
+
"ask": entry["Close"][0],
|
|
474
|
+
"bid": entry["Close"][0],
|
|
475
|
+
}
|
|
476
|
+
else:
|
|
477
|
+
|
|
478
|
+
if self.backtest_mode:
|
|
479
|
+
return data_provider.get_backtest_data(
|
|
480
|
+
backtest_index_date=date,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
else:
|
|
484
|
+
return data_provider.get_data(date=date)
|
|
485
|
+
|
|
486
|
+
def get_ohlcv_data(
|
|
487
|
+
self,
|
|
488
|
+
symbol: str,
|
|
489
|
+
market: str = None,
|
|
490
|
+
time_frame: str = None,
|
|
491
|
+
date: Optional[datetime] = None,
|
|
492
|
+
start_date: Optional[datetime] = None,
|
|
493
|
+
end_date: Optional[datetime] = None,
|
|
494
|
+
window_size: Optional[int] = None,
|
|
495
|
+
pandas: bool = False,
|
|
496
|
+
add_pandas_index: bool = True,
|
|
497
|
+
add_datetime_column: bool = False,
|
|
498
|
+
):
|
|
499
|
+
"""
|
|
500
|
+
Function to get OHLCV data from the data provider.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
symbol (str): The symbol to get OHLCV data for.
|
|
504
|
+
market (str): The market to get OHLCV data for.
|
|
505
|
+
time_frame (str): The time frame to get OHLCV data for.
|
|
506
|
+
date (datetime): The date to get OHLCV data for.
|
|
507
|
+
start_date (datetime): The start date for the OHLCV data.
|
|
508
|
+
end_date (datetime): The end date for the OHLCV data.
|
|
509
|
+
window_size (int): The window size for the OHLCV data.
|
|
510
|
+
pandas (bool): Whether to return the data as a pandas DataFrame.
|
|
511
|
+
add_pandas_index (bool): Whether to add a pandas index to
|
|
512
|
+
the DataFrame if pandas is True.
|
|
513
|
+
add_datetime_column (bool): Whether to add a datetime column
|
|
514
|
+
to the DataFrame if pandas is True.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
DataFrame: The OHLCV data for the given symbol and market.
|
|
518
|
+
"""
|
|
519
|
+
data_provider = self.data_provider_index.get_ohlcv_data_provider(
|
|
520
|
+
symbol=symbol,
|
|
521
|
+
market=market,
|
|
522
|
+
time_frame=time_frame
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
if data_provider is None:
|
|
526
|
+
if market is not None:
|
|
527
|
+
raise OperationalException(
|
|
528
|
+
"No OHLCV data provider found "
|
|
529
|
+
f"for symbol: {symbol} and market: {market}"
|
|
530
|
+
)
|
|
531
|
+
else:
|
|
532
|
+
raise OperationalException(
|
|
533
|
+
f"No OHLCV data provider found for symbol: {symbol}"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
if self.backtest_mode:
|
|
537
|
+
data = data_provider.get_backtest_data(
|
|
538
|
+
backtest_index_date=date,
|
|
539
|
+
backtest_start_date=start_date,
|
|
540
|
+
backtest_end_date=end_date,
|
|
541
|
+
)
|
|
542
|
+
else:
|
|
543
|
+
data = data_provider.get_data(
|
|
544
|
+
date=date,
|
|
545
|
+
start_date=start_date,
|
|
546
|
+
end_date=end_date,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
if pandas:
|
|
550
|
+
if isinstance(data, pl.DataFrame):
|
|
551
|
+
return convert_polars_to_pandas(
|
|
552
|
+
data,
|
|
553
|
+
add_index=add_pandas_index,
|
|
554
|
+
add_datetime_column=add_datetime_column
|
|
555
|
+
)
|
|
556
|
+
else:
|
|
557
|
+
return data
|
|
558
|
+
|
|
559
|
+
if isinstance(data, pd.DataFrame):
|
|
560
|
+
# Convert to Polars DataFrame for consistency
|
|
561
|
+
data.index.name = "Datetime"
|
|
562
|
+
data = data.reset_index()
|
|
563
|
+
data = pl.from_pandas(data)
|
|
564
|
+
|
|
565
|
+
return data
|
|
566
|
+
|
|
567
|
+
def get_backtest_data(
|
|
568
|
+
self,
|
|
569
|
+
data_source: DataSource,
|
|
570
|
+
backtest_index_date: datetime = None,
|
|
571
|
+
start_date: datetime = None,
|
|
572
|
+
end_date: datetime = None,
|
|
573
|
+
):
|
|
574
|
+
"""
|
|
575
|
+
Function to get backtest data from the data provider.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
data_source (DataSource): The data source specification that
|
|
579
|
+
matches a data provider.
|
|
580
|
+
backtest_index_date (datetime): The current date of the backtest.
|
|
581
|
+
start_date (datetime): The start date for the data.
|
|
582
|
+
end_date (datetime): The end date for the data.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
DataFrame: The backtest data for the given symbol and market.
|
|
586
|
+
"""
|
|
587
|
+
data_provider = self.data_provider_index.get(data_source=data_source)
|
|
588
|
+
|
|
589
|
+
if data_provider is None:
|
|
590
|
+
self._throw_no_data_provider_exception(data_source.to_dict())
|
|
591
|
+
|
|
592
|
+
return data_provider.get_backtest_data(
|
|
593
|
+
backtest_index_date=backtest_index_date,
|
|
594
|
+
backtest_start_date=start_date,
|
|
595
|
+
backtest_end_date=end_date,
|
|
596
|
+
data_source=data_source
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
def get_vectorized_backtest_data(
|
|
600
|
+
self,
|
|
601
|
+
data_sources: List[DataSource],
|
|
602
|
+
start_date: datetime = None,
|
|
603
|
+
end_date: datetime = None,
|
|
604
|
+
) -> Dict[str, Any]:
|
|
605
|
+
"""
|
|
606
|
+
Function to get vectorized backtest data from the data provider.
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
data_sources (List[DataSource]): The data sources to get
|
|
610
|
+
backtest data for.
|
|
611
|
+
start_date (datetime): The start date for the backtest data.
|
|
612
|
+
end_date (datetime): The end date for the backtest data.
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
Dict[str, Any]: The vectorized backtest data for the
|
|
616
|
+
given data sources.
|
|
617
|
+
"""
|
|
618
|
+
vectorized_data = {}
|
|
619
|
+
|
|
620
|
+
for data_source in data_sources:
|
|
621
|
+
data_start_date = data_source.create_start_date_data(start_date)
|
|
622
|
+
backtest_data = self.get_backtest_data(
|
|
623
|
+
data_source=data_source,
|
|
624
|
+
start_date=data_start_date,
|
|
625
|
+
end_date=end_date,
|
|
626
|
+
)
|
|
627
|
+
vectorized_data[data_source.get_identifier()] = backtest_data
|
|
628
|
+
|
|
629
|
+
return vectorized_data
|
|
630
|
+
|
|
631
|
+
def _throw_no_data_provider_exception(self, params):
|
|
632
|
+
"""
|
|
633
|
+
Raise an exception if no data provider is found for the given params
|
|
634
|
+
"""
|
|
635
|
+
non_null_params = {k: v for k, v in params.items() if v is not None}
|
|
636
|
+
if len(non_null_params) == 0:
|
|
637
|
+
raise OperationalException(
|
|
638
|
+
"No data provider found for the given parameters"
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
params = ", ".join(
|
|
642
|
+
[f"{k}: {v}" for k, v in non_null_params.items()]
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
raise OperationalException(
|
|
646
|
+
f"No data provider found for the given parameters: {params}"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def register_data_provider(
|
|
650
|
+
self, data_source: DataSource, data_provider: DataProvider
|
|
651
|
+
) -> DataProvider:
|
|
652
|
+
"""
|
|
653
|
+
Function to directly register a data provider for a given data source.
|
|
654
|
+
|
|
655
|
+
This method will not check if the data provider supports the
|
|
656
|
+
data source. It will directly register the data provider in the index.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
data_source (DataSource): The data source to register the
|
|
660
|
+
data provider for.
|
|
661
|
+
data_provider (DataProvider): The data provider to register.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
DataProvider: The registered data provider.
|
|
665
|
+
"""
|
|
666
|
+
data_provider = data_provider.copy(data_source)
|
|
667
|
+
self.data_provider_index.data_providers_lookup[data_source] = \
|
|
668
|
+
data_provider
|
|
669
|
+
return data_provider
|
|
670
|
+
|
|
671
|
+
def add_data_provider(
|
|
672
|
+
self, data_provider: DataProvider, priority: int = 3
|
|
673
|
+
):
|
|
674
|
+
"""
|
|
675
|
+
Add a data provider to the service.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
data_provider (DataProvider): The data provider to add.
|
|
679
|
+
priority (int): The priority of the data provider.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
None
|
|
683
|
+
"""
|
|
684
|
+
data_provider.priority = priority
|
|
685
|
+
data_provider.config = self.configuration_service.get_config()
|
|
686
|
+
self.data_provider_index.add(data_provider)
|
|
687
|
+
|
|
688
|
+
def index_data_providers(self, data_sources: List[DataSource]):
|
|
689
|
+
"""
|
|
690
|
+
Index the data providers in the service.
|
|
691
|
+
This will create a fast lookup index for the data providers
|
|
692
|
+
based on the given parameters.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
data_sources (List[DataSource]): The data sources to index.
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
None
|
|
699
|
+
"""
|
|
700
|
+
|
|
701
|
+
for data_source in data_sources:
|
|
702
|
+
self.data_provider_index.register(data_source)
|
|
703
|
+
logger.debug(
|
|
704
|
+
"Registered data provider for data source: {data_source}"
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
def index_backtest_data_providers(
|
|
708
|
+
self,
|
|
709
|
+
data_sources: List[DataSource],
|
|
710
|
+
backtest_date_range: BacktestDateRange,
|
|
711
|
+
show_progress: bool = True
|
|
712
|
+
):
|
|
713
|
+
"""
|
|
714
|
+
Index the data providers in the service.
|
|
715
|
+
This will create a fast lookup index for the data providers
|
|
716
|
+
based on the given parameters.
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
data_sources (List[DataSource]): The data sources to index.
|
|
720
|
+
backtest_date_range (BacktestDateRange): The date range for the
|
|
721
|
+
backtest data providers.
|
|
722
|
+
show_progress (bool): Whether to show progress while indexing
|
|
723
|
+
the data providers.
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
None
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
# Filter out duplicate data_sources
|
|
730
|
+
unique_data_sources = set(data_sources)
|
|
731
|
+
|
|
732
|
+
if show_progress:
|
|
733
|
+
|
|
734
|
+
for data_source in tqdm(
|
|
735
|
+
unique_data_sources,
|
|
736
|
+
desc="Registering backtest data providers for data sources",
|
|
737
|
+
colour="green"
|
|
738
|
+
):
|
|
739
|
+
self.data_provider_index.register_backtest_data_source(
|
|
740
|
+
data_source, backtest_date_range
|
|
741
|
+
)
|
|
742
|
+
logger.debug(
|
|
743
|
+
"Registered backtest "
|
|
744
|
+
f"data provider for data source: {data_source}"
|
|
745
|
+
)
|
|
746
|
+
else:
|
|
747
|
+
for data_source in unique_data_sources:
|
|
748
|
+
self.data_provider_index.register_backtest_data_source(
|
|
749
|
+
data_source, backtest_date_range
|
|
750
|
+
)
|
|
751
|
+
logger.debug(
|
|
752
|
+
"Registered backtest "
|
|
753
|
+
f"data provider for data source: {data_source}"
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
self.backtest_mode = True
|
|
757
|
+
|
|
758
|
+
def prepare_backtest_data(
|
|
759
|
+
self,
|
|
760
|
+
backtest_date_range: BacktestDateRange,
|
|
761
|
+
show_progress: bool = True
|
|
762
|
+
):
|
|
763
|
+
"""
|
|
764
|
+
Prepare the backtest data for all registered data providers.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
backtest_date_range (BacktestDateRange): The date range for the
|
|
768
|
+
backtest data.
|
|
769
|
+
show_progress (bool): Whether to show progress while preparing
|
|
770
|
+
the backtest data.
|
|
771
|
+
|
|
772
|
+
Raises:
|
|
773
|
+
OperationalException: If no data providers are registered.
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
None
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
if len(self.data_provider_index.data_providers_lookup) == 0:
|
|
780
|
+
raise OperationalException(
|
|
781
|
+
"No data providers registered. "
|
|
782
|
+
"Please register at least one data provider before preparing "
|
|
783
|
+
"backtest data."
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
logger.info(
|
|
787
|
+
"Preparing backtest data for all registered data providers"
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
if show_progress:
|
|
791
|
+
for data_source, data_provider in tqdm(
|
|
792
|
+
self.data_provider_index.get_all(),
|
|
793
|
+
desc="Preparing backtest data",
|
|
794
|
+
colour="green"
|
|
795
|
+
):
|
|
796
|
+
try:
|
|
797
|
+
data_provider.prepare_backtest_data(
|
|
798
|
+
backtest_start_date=backtest_date_range.start_date,
|
|
799
|
+
backtest_end_date=backtest_date_range.end_date
|
|
800
|
+
)
|
|
801
|
+
except Exception as e:
|
|
802
|
+
logger.error(
|
|
803
|
+
f"Error preparing backtest data for {data_source}: {e}"
|
|
804
|
+
)
|
|
805
|
+
else:
|
|
806
|
+
for data_source, data_provider \
|
|
807
|
+
in self.data_provider_index.get_all():
|
|
808
|
+
|
|
809
|
+
try:
|
|
810
|
+
data_provider.prepare_backtest_data(
|
|
811
|
+
backtest_start_date=backtest_date_range.start_date,
|
|
812
|
+
backtest_end_date=backtest_date_range.end_date
|
|
813
|
+
)
|
|
814
|
+
except Exception as e:
|
|
815
|
+
logger.error(
|
|
816
|
+
f"Error preparing backtest data for {data_source}: {e}"
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
def get_data_files(self):
|
|
820
|
+
"""
|
|
821
|
+
Function to get the data files for the market data sources.
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
List[str]: A list of file paths for the data files.
|
|
825
|
+
"""
|
|
826
|
+
data_files = []
|
|
827
|
+
|
|
828
|
+
for market_data_source in self.data_provider_index.data_providers:
|
|
829
|
+
if hasattr(market_data_source, 'file_path') and \
|
|
830
|
+
market_data_source.file_path is not None:
|
|
831
|
+
data_files.append(market_data_source.file_path)
|
|
832
|
+
|
|
833
|
+
return data_files
|
|
834
|
+
|
|
835
|
+
def get_all_registered_data_providers(self) -> List[DataProvider]:
|
|
836
|
+
"""
|
|
837
|
+
Function to get all registered data providers.
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
List[DataProvider]: A list of all registered data providers.
|
|
841
|
+
"""
|
|
842
|
+
return self.data_provider_index.get_all()
|
|
843
|
+
|
|
844
|
+
def reset(self):
|
|
845
|
+
"""
|
|
846
|
+
Function to reset all the data providers and the data provider
|
|
847
|
+
lookup index.
|
|
848
|
+
"""
|
|
849
|
+
self.data_provider_index.reset()
|
|
850
|
+
self.backtest_mode = False
|