investing-algorithm-framework 1.5__py3-none-any.whl → 7.25.6__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 (276) hide show
  1. investing_algorithm_framework/__init__.py +192 -16
  2. investing_algorithm_framework/analysis/__init__.py +16 -0
  3. investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
  4. investing_algorithm_framework/analysis/data.py +170 -0
  5. investing_algorithm_framework/analysis/markdown.py +91 -0
  6. investing_algorithm_framework/analysis/ranking.py +298 -0
  7. investing_algorithm_framework/app/__init__.py +29 -4
  8. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  9. investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
  10. investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
  11. investing_algorithm_framework/app/app.py +2220 -379
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1724 -0
  14. investing_algorithm_framework/app/eventloop.py +620 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  31. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  32. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  33. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  34. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  35. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
  36. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  37. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  39. investing_algorithm_framework/app/strategy.py +867 -60
  40. investing_algorithm_framework/app/task.py +5 -3
  41. investing_algorithm_framework/app/web/__init__.py +2 -1
  42. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  43. investing_algorithm_framework/app/web/controllers/orders.py +3 -2
  44. investing_algorithm_framework/app/web/controllers/positions.py +2 -2
  45. investing_algorithm_framework/app/web/create_app.py +4 -2
  46. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  47. investing_algorithm_framework/cli/__init__.py +0 -0
  48. investing_algorithm_framework/cli/cli.py +231 -0
  49. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  50. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  51. investing_algorithm_framework/cli/initialize_app.py +603 -0
  52. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  53. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  55. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  56. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  58. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  59. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  60. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  61. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  62. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  63. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  64. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  65. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  66. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  67. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  68. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  69. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  70. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  71. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  72. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  73. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  74. investing_algorithm_framework/create_app.py +40 -7
  75. investing_algorithm_framework/dependency_container.py +100 -47
  76. investing_algorithm_framework/domain/__init__.py +97 -30
  77. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  78. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  79. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  81. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  82. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  83. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  84. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  87. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  88. investing_algorithm_framework/domain/config.py +59 -136
  89. investing_algorithm_framework/domain/constants.py +18 -37
  90. investing_algorithm_framework/domain/data_provider.py +334 -0
  91. investing_algorithm_framework/domain/data_structures.py +42 -0
  92. investing_algorithm_framework/domain/exceptions.py +51 -1
  93. investing_algorithm_framework/domain/models/__init__.py +26 -19
  94. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  95. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  96. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  97. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  98. investing_algorithm_framework/domain/models/event.py +35 -0
  99. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  100. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  101. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  102. investing_algorithm_framework/domain/models/order/order.py +198 -65
  103. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  104. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  105. investing_algorithm_framework/domain/models/portfolio/__init__.py +6 -2
  106. investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
  107. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
  108. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  109. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  110. investing_algorithm_framework/domain/models/position/position.py +20 -0
  111. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  112. investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
  113. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  114. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  115. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  116. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  117. investing_algorithm_framework/domain/models/strategy_profile.py +19 -141
  118. investing_algorithm_framework/domain/models/time_frame.py +94 -98
  119. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  120. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  121. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  122. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  123. investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
  124. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  125. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  126. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  127. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  128. investing_algorithm_framework/domain/order_executor.py +112 -0
  129. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  130. investing_algorithm_framework/domain/services/__init__.py +11 -0
  131. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  132. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  133. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  134. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  135. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  136. investing_algorithm_framework/domain/strategy.py +1 -29
  137. investing_algorithm_framework/domain/utils/__init__.py +15 -5
  138. investing_algorithm_framework/domain/utils/csv.py +22 -0
  139. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  140. investing_algorithm_framework/domain/utils/dates.py +57 -0
  141. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  142. investing_algorithm_framework/domain/utils/polars.py +53 -0
  143. investing_algorithm_framework/domain/utils/random.py +29 -0
  144. investing_algorithm_framework/download_data.py +244 -0
  145. investing_algorithm_framework/infrastructure/__init__.py +37 -11
  146. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  147. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  148. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  149. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  150. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  151. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  152. investing_algorithm_framework/infrastructure/models/__init__.py +7 -3
  153. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  154. investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
  155. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  156. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  157. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  158. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -2
  159. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
  160. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
  161. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  162. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  163. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  164. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  165. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  166. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  167. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  168. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  169. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  170. investing_algorithm_framework/infrastructure/repositories/__init__.py +10 -4
  171. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  172. investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
  173. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  174. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  175. investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
  176. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  177. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  178. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  179. investing_algorithm_framework/infrastructure/services/__init__.py +9 -4
  180. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  181. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  182. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  183. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  184. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  185. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  186. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  187. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  188. investing_algorithm_framework/services/__init__.py +123 -15
  189. investing_algorithm_framework/services/configuration_service.py +77 -11
  190. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  191. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  192. investing_algorithm_framework/services/market_credential_service.py +40 -0
  193. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  194. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  195. investing_algorithm_framework/services/metrics/beta.py +0 -0
  196. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  197. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  198. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  199. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  200. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  201. investing_algorithm_framework/services/metrics/generate.py +358 -0
  202. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  203. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  204. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  205. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  206. investing_algorithm_framework/services/metrics/returns.py +452 -0
  207. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  208. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  209. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  210. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  211. investing_algorithm_framework/services/metrics/trades.py +473 -0
  212. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  213. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  214. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  215. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  216. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  217. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  218. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  219. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  220. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  221. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  222. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  223. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  224. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  225. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  226. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  227. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  228. investing_algorithm_framework/services/positions/__init__.py +7 -0
  229. investing_algorithm_framework/services/positions/position_service.py +210 -0
  230. investing_algorithm_framework/services/repository_service.py +8 -2
  231. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  232. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  233. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  234. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  235. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  237. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  238. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  239. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  240. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  241. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  242. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  243. investing_algorithm_framework/app/algorithm.py +0 -630
  244. investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
  245. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  246. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  247. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
  248. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  249. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  250. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  251. investing_algorithm_framework/domain/models/trade.py +0 -78
  252. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  253. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  254. investing_algorithm_framework/domain/singleton.py +0 -9
  255. investing_algorithm_framework/domain/utils/backtesting.py +0 -82
  256. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  257. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  258. investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
  259. investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
  260. investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
  261. investing_algorithm_framework/services/backtest_service.py +0 -268
  262. investing_algorithm_framework/services/market_data_service.py +0 -77
  263. investing_algorithm_framework/services/order_backtest_service.py +0 -122
  264. investing_algorithm_framework/services/order_service.py +0 -752
  265. investing_algorithm_framework/services/portfolio_service.py +0 -164
  266. investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
  267. investing_algorithm_framework/services/position_cost_service.py +0 -5
  268. investing_algorithm_framework/services/position_service.py +0 -63
  269. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
  270. investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
  271. investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
  272. investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
  273. investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
  274. /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
  275. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  276. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,1058 @@
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_data_source_and_backtest_data_provider(
130
+ self, data_source: DataSource, data_provider: DataProvider
131
+ ):
132
+ """
133
+ Register a data source and its corresponding data provider
134
+ in the DataProvider Index.
135
+
136
+ This method will directly register the given data provider
137
+ for the specified data source without checking for compatibility.
138
+
139
+ Args:
140
+ data_source (DataSource): The data source to register.
141
+ data_provider (DataProvider): The data provider to associate
142
+ with the data source.
143
+ Returns:
144
+ DataProvider: The registered data provider.
145
+ """
146
+ data_provider = data_provider.copy(data_source)
147
+ self.data_providers_lookup[data_source] = data_provider
148
+ symbol = data_source.symbol
149
+ market = data_source.market
150
+ time_frame = data_source.time_frame
151
+
152
+ if DataType.OHLCV.equals(data_source.data_type):
153
+ if symbol not in self.ohlcv_data_providers:
154
+ self.ohlcv_data_providers[(symbol, market)] = data_provider
155
+ self.ohlcv_data_providers_no_market[symbol] = data_provider
156
+ self.ohlcv_data_providers_with_timeframe[
157
+ (symbol, market, time_frame)
158
+ ] = data_provider
159
+ else:
160
+ try:
161
+ # If the symbol already exists, we can update the provider
162
+ # has a more granular timeframe
163
+ existing_provider = self.ohlcv_data_providers[
164
+ (symbol, market)
165
+ ]
166
+
167
+ if existing_provider.time_frame > data_provider.time_frame:
168
+ self.ohlcv_data_providers[(symbol, market)] = \
169
+ data_provider
170
+
171
+ existing_provider = self.ohlcv_data_providers_no_market[
172
+ symbol
173
+ ]
174
+
175
+ if existing_provider.time_frame > data_provider.time_frame:
176
+ self.ohlcv_data_providers_no_market[symbol] = \
177
+ data_provider
178
+
179
+ time_frame_key = (symbol, market, time_frame)
180
+ self.ohlcv_data_providers_with_timeframe[
181
+ time_frame_key
182
+ ] = data_provider
183
+
184
+ except Exception:
185
+ # If the existing provider does not have a time_frame
186
+ # attribute, we can safely ignore this
187
+ pass
188
+
189
+ elif DataType.TICKER.equals(data_source.data_type):
190
+ if symbol not in self.ticker_data_providers:
191
+ self.ticker_data_providers[symbol] = data_provider
192
+
193
+ return data_provider
194
+
195
+ def register_backtest_data_source(
196
+ self,
197
+ data_source: DataSource,
198
+ backtest_date_range: BacktestDateRange
199
+ ) -> DataProvider:
200
+ """
201
+ Register a backtest data source for a given market and symbol.
202
+
203
+ This method will also check if the data provider supports
204
+ the market. If no data provider is found for the market and symbol,
205
+ it will raise an ImproperlyConfigured exception.
206
+
207
+ Args:
208
+ data_source (DataSource): The data source to register the
209
+ backtest data provider for.
210
+ backtest_date_range (BacktestDateRange): The date range for the
211
+ backtest data provider.
212
+
213
+ Returns:
214
+ DataProvider: The registered data provider.
215
+ """
216
+ matches = []
217
+
218
+ for data_provider in self.data_providers:
219
+
220
+ if data_provider.has_data(
221
+ data_source,
222
+ start_date=backtest_date_range.start_date,
223
+ end_date=backtest_date_range.end_date
224
+ ):
225
+ matches.append(data_provider)
226
+
227
+ if len(matches) == 0:
228
+ params = data_source.to_dict()
229
+ raise ImproperlyConfigured(
230
+ f"No data provider found for given parameters: {params}."
231
+ f" Please make sure that you have registered a data provider "
232
+ f"provider for the defined datasource. If you are using a "
233
+ "custom data provider, make sure it has a "
234
+ "data_provider_identifier set"
235
+ )
236
+
237
+ # Sort by priority and pick the best one (lowest priority first)
238
+ best_provider = sorted(matches, key=lambda x: x.priority)[0]
239
+ best_provider = best_provider.copy(data_source)
240
+ self.data_providers_lookup[data_source] = best_provider
241
+
242
+ symbol = data_source.symbol
243
+ market = data_source.market
244
+ time_frame = data_source.time_frame
245
+
246
+ if DataType.OHLCV.equals(data_source.data_type):
247
+
248
+ if symbol not in self.ohlcv_data_providers:
249
+ self.ohlcv_data_providers[(symbol, market)] = best_provider
250
+ self.ohlcv_data_providers_no_market[symbol] = best_provider
251
+ self.ohlcv_data_providers_with_timeframe[
252
+ (symbol, market, time_frame)
253
+ ] = best_provider
254
+ else:
255
+ try:
256
+ # If the symbol already exists, we can update the provider
257
+ # has a more granular timeframe
258
+ existing_provider = self.ohlcv_data_providers[
259
+ (symbol.upper(), market.upper())
260
+ ]
261
+ if existing_provider.time_frame > best_provider.time_frame:
262
+ self.ohlcv_data_providers[
263
+ (symbol.upper(), market.upper())
264
+ ] =\
265
+ best_provider
266
+
267
+ existing_provider = self.ohlcv_data_providers_no_market[
268
+ symbol
269
+ ]
270
+
271
+ if existing_provider.time_frame > best_provider.time_frame:
272
+ self.ohlcv_data_providers_no_market[symbol] = \
273
+ best_provider
274
+
275
+ time_frame_key = (symbol, market, data_source.time_frame)
276
+ self.ohlcv_data_providers_with_timeframe[
277
+ time_frame_key
278
+ ] = best_provider
279
+
280
+ except Exception:
281
+ # If the existing provider does not have a time_frame
282
+ # attribute, we can safely ignore this
283
+ pass
284
+
285
+ elif DataType.TICKER.equals(data_source.data_type):
286
+ if symbol not in self.ticker_data_providers:
287
+ self.ticker_data_providers[symbol] = best_provider
288
+
289
+ return best_provider
290
+
291
+ def get_data_provider(
292
+ self, data_source: DataSource
293
+ ) -> Optional[DataProvider]:
294
+ """
295
+ Get the data provider for a given data source.
296
+
297
+ Args:
298
+ data_source (DataSource): The data source to get
299
+ the data provider for.
300
+
301
+ Returns:
302
+ DataProvider: The data provider for the market and symbol,
303
+ """
304
+ return self.data_providers_lookup.get(data_source, None)
305
+
306
+ def get(self, data_source: DataSource) -> Optional[DataProvider]:
307
+ """
308
+ Get the data provider for a given data source.
309
+
310
+ Args:
311
+ data_source (DataSource): The data source to get the
312
+ data provider for.
313
+
314
+ Returns:
315
+ DataProvider: The data provider for the market and symbol,
316
+ """
317
+ return self.data_providers_lookup.get(data_source, None)
318
+
319
+ def find(self, data_source: DataSource) -> Optional[DataProvider]:
320
+ """
321
+ Find a data provider for a given data source.
322
+
323
+ Args:
324
+ data_source (DataSource): The data source to find the
325
+ data provider for.
326
+
327
+ Returns:
328
+ DataProvider: The data provider for the market and symbol,
329
+ or None if no provider is found.
330
+ """
331
+ return self.data_providers_lookup.get(data_source, None)
332
+
333
+ def get_all(self) -> List[Tuple[DataSource, DataProvider]]:
334
+ """
335
+ Get all registered data providers with corresponding DataSource.
336
+
337
+ Returns:
338
+ List[Tuple[DataSource, DataProvider]]: A list of all
339
+ data providers with corresponding data sources.
340
+ """
341
+ return [
342
+ (data_source, data_provider)
343
+ for data_source, data_provider
344
+ in self.data_providers_lookup.items()
345
+ ]
346
+
347
+ def reset(self):
348
+ """
349
+ Function to reset the order executor lookup table
350
+
351
+ Returns:
352
+ None
353
+ """
354
+ self.data_providers_lookup = defaultdict()
355
+ self.data_providers = []
356
+
357
+ def __len__(self):
358
+ """
359
+ Returns the number of data providers in the index.
360
+
361
+ Returns:
362
+ int: The number of data providers.
363
+ """
364
+ return len(self.data_providers_lookup)
365
+
366
+ def get_ohlcv_data_provider(
367
+ self,
368
+ symbol: str,
369
+ market: Optional[str] = None,
370
+ time_frame: Optional[str] = None
371
+ ) -> Optional[DataProvider]:
372
+ """
373
+ Get the OHLCV data provider for a given symbol and market.
374
+
375
+ Args:
376
+ symbol (str): The symbol to get the data provider for.
377
+ market (Optional[str]): The market to get the data provider for.
378
+ time_frame (Optional[str]): The time frame to get the
379
+ data provider for.
380
+
381
+ Returns:
382
+ DataProvider: The OHLCV data provider for the symbol and market,
383
+ or None if no provider is found.
384
+ """
385
+
386
+ if market is not None and time_frame is not None:
387
+ time_frame = TimeFrame.from_value(time_frame)
388
+ return self.ohlcv_data_providers_with_timeframe.get(
389
+ (symbol, market, time_frame), None
390
+ )
391
+
392
+ if market is None:
393
+ # If no market is specified
394
+ return self.ohlcv_data_providers_no_market.get(symbol, None)
395
+
396
+ return self.ohlcv_data_providers.get((symbol, market), None)
397
+
398
+ def get_ticker_data_provider(
399
+ self, symbol: str, market: str
400
+ ) -> Optional[DataProvider]:
401
+ """
402
+ Get the ticker data provider for a given symbol and market.
403
+
404
+ Args:
405
+ symbol (str): The symbol to get the data provider for.
406
+ market (str): The market to get the data provider for.
407
+
408
+ Returns:
409
+ DataProvider: The ticker data provider for the symbol and market,
410
+ or None if no provider is found.
411
+ """
412
+ return self.ticker_data_providers.get(symbol, None)
413
+
414
+
415
+ class DataProviderService:
416
+ data_provider_index: DataProviderIndex = None
417
+ backtest_mode = False
418
+
419
+ @staticmethod
420
+ def from_data_pairs(data_pairs: List[Tuple[DataSource, DataProvider]]):
421
+ """
422
+ Create a DataProviderService from a list of data source and
423
+ data provider pairs.
424
+
425
+ Args:
426
+ data_pairs (List[Tuple[DataSource, DataProvider]]): A list of
427
+ data source and data provider pairs.
428
+ Returns:
429
+ DataProviderService: The created DataProviderService.
430
+ """
431
+ service = DataProviderService()
432
+ for data_source, data_provider in data_pairs:
433
+ service.register_data_provider(data_source, data_provider)
434
+ return service
435
+
436
+ def __init__(
437
+ self,
438
+ configuration_service=None,
439
+ market_credential_service=None,
440
+ default_data_providers: List[DataProvider] = [],
441
+ ):
442
+ """
443
+ Initialize the DataProviderService with a list of data providers.
444
+
445
+ Args:
446
+ default_data_providers (List[DataProvider]): A list of default
447
+ data providers to use.
448
+ """
449
+ self.default_data_providers = default_data_providers
450
+ self.data_provider_index = DataProviderIndex(default_data_providers)
451
+ self.configuration_service = configuration_service
452
+ self.market_credential_service = market_credential_service
453
+
454
+ def initialize(self, data_sources, data_providers):
455
+ """
456
+ Initialize the DataProviderService with data sources and
457
+ data providers.
458
+
459
+ Args:
460
+ data_sources (List[DataSource]): A list of data sources to
461
+ index.
462
+ data_providers (List[DataProvider]): A list of data providers
463
+ to index.
464
+
465
+ Returns:
466
+ None
467
+ """
468
+ for data_provider in data_providers:
469
+ self.add_data_provider(data_provider)
470
+
471
+ self.index_data_providers(data_sources)
472
+
473
+ def register_data_source_and_backtest_data_provider(
474
+ self, data_source, data_provider
475
+ ):
476
+ """
477
+ Register a data source and its corresponding data provider
478
+ in the DataProvider Service.
479
+
480
+ This method will directly register the given data provider
481
+ for the specified data source without checking for compatibility.
482
+
483
+ Args:
484
+ data_source (DataSource): The data source to register.
485
+ data_provider (DataProvider): The data provider to associate
486
+ with the data source.
487
+ Returns:
488
+ DataProvider: The registered data provider.
489
+ """
490
+ return self.data_provider_index\
491
+ .register_data_source_and_backtest_data_provider(
492
+ data_source, data_provider
493
+ )
494
+
495
+ def get_data_sources(self, strategies) -> List[DataSource]:
496
+ """
497
+ Get all data sources for the given strategies.
498
+
499
+ Args:
500
+ strategies: The strategies to get the data sources for.
501
+
502
+ Returns:
503
+ List[DataSource]: A list of data sources for the strategies.
504
+ """
505
+ data_sources = []
506
+
507
+ for strategy in strategies:
508
+ data_sources.extend(strategy.get_data_sources())
509
+
510
+ return list(set(data_sources))
511
+
512
+ def get_data_providers(self, strategies) -> List[DataProvider]:
513
+ """
514
+ Get all data providers for the given strategies.
515
+
516
+ Args:
517
+ strategies: The strategies to get the data providers for.
518
+
519
+ Returns:
520
+ List[DataProvider]: A list of data providers for the strategies.
521
+ """
522
+ data_providers = []
523
+ data_sources = self.get_data_sources(strategies)
524
+
525
+ for data_source in data_sources:
526
+ data_provider = self.get(data_source)
527
+ if data_provider is not None:
528
+ data_providers.append(data_provider)
529
+
530
+ return data_providers
531
+
532
+ def get_data_provider(
533
+ self, data_source: DataSource
534
+ ) -> Optional[DataProvider]:
535
+ """
536
+ Get a registered data provider by its data source.
537
+
538
+ Args:
539
+ data_source (DataSource): The data source to get the
540
+ data provider for.
541
+
542
+ Returns:
543
+ Optional[DataProvider]: The registered data provider for
544
+ the data source, or None if not found.
545
+ """
546
+ return self.data_provider_index.get(data_source)
547
+
548
+ def get(self, data_source: DataSource) -> Optional[DataProvider]:
549
+ """
550
+ Get a registered data provider by its data source.
551
+
552
+ Args:
553
+ data_source (DataSource): The data source to get the
554
+ data provider for.
555
+
556
+ Returns:
557
+ Optional[DataProvider]: The registered data provider for
558
+ the data source, or None if not found.
559
+ """
560
+ return self.data_provider_index.get(data_source)
561
+
562
+ def get_data(
563
+ self,
564
+ data_source: DataSource,
565
+ date: datetime = None,
566
+ start_date: datetime = None,
567
+ end_date: datetime = None,
568
+ ):
569
+ """
570
+ Function to get data from the data provider.
571
+
572
+ Args:
573
+ data_source (DataSource): The data source specification that
574
+ matches a data provider.
575
+ date (datetime): The date to get data for.
576
+ start_date (datetime): The start date for the data.
577
+ end_date (datetime): The end date for the data.
578
+
579
+ Returns:
580
+ DataFrame: The data for the given symbol and market.
581
+ """
582
+ data_provider = self.data_provider_index.get(data_source=data_source)
583
+
584
+ if data_provider is None:
585
+ dict_data = data_source.to_dict()
586
+ self._throw_no_data_provider_exception(dict_data)
587
+
588
+ if self.configuration_service is not None:
589
+ data_provider.config = self.configuration_service.get_config()
590
+
591
+ return data_provider.get_data(
592
+ date=date,
593
+ start_date=start_date,
594
+ end_date=end_date,
595
+ save=data_source.save,
596
+ )
597
+
598
+ def get_ticker_data(self, symbol, market, date):
599
+ """
600
+ Function to get a ticker for a given data source.
601
+ The data source should have its market and symbol defined.
602
+ All other attributes are ignored of the data source
603
+ """
604
+ data_provider = self.data_provider_index.get_ticker_data_provider(
605
+ symbol=symbol, market=market
606
+ )
607
+
608
+ if data_provider is None:
609
+ ohlcv_data_provider = self.data_provider_index.\
610
+ get_ohlcv_data_provider(
611
+ symbol=symbol, market=market
612
+ )
613
+
614
+ if ohlcv_data_provider is None:
615
+ raise OperationalException(
616
+ "No ticker data provider found "
617
+ f"for symbol: {symbol} and market: {market} "
618
+ f"on date: {date}"
619
+ )
620
+ else:
621
+ if self.backtest_mode:
622
+ data = ohlcv_data_provider.get_backtest_data(
623
+ backtest_index_date=date,
624
+ )
625
+
626
+ if isinstance(data, pd.DataFrame):
627
+ # Convert to Polars DataFrame for consistency
628
+ data.index.name = "Datetime"
629
+ data = data.reset_index()
630
+ data = pl.from_pandas(data)
631
+ entry = data[-1]
632
+ return {
633
+ "symbol": symbol,
634
+ "market": market,
635
+ "datetime": entry["Datetime"][0],
636
+ "open": entry["Open"][0],
637
+ "high": entry["High"][0],
638
+ "low": entry["Low"][0],
639
+ "close": entry["Close"][0],
640
+ "volume": entry["Close"][0],
641
+ "ask": entry["Close"][0],
642
+ "bid": entry["Close"][0],
643
+ }
644
+ else:
645
+ data = ohlcv_data_provider.get_data(
646
+ date=date,
647
+ )
648
+
649
+ if isinstance(data, pd.DataFrame):
650
+ # Convert to Polars DataFrame for consistency
651
+ data.index.name = "Datetime"
652
+ data = data.reset_index()
653
+ data = pl.from_pandas(data)
654
+
655
+ entry = data[-1]
656
+ return {
657
+ "symbol": symbol,
658
+ "market": market,
659
+ "datetime": entry["Datetime"][0],
660
+ "open": entry["Open"][0],
661
+ "high": entry["High"][0],
662
+ "low": entry["Low"][0],
663
+ "close": entry["Close"][0],
664
+ "volume": entry["Close"][0],
665
+ "ask": entry["Close"][0],
666
+ "bid": entry["Close"][0],
667
+ }
668
+ else:
669
+
670
+ if self.backtest_mode:
671
+ return data_provider.get_backtest_data(
672
+ backtest_index_date=date,
673
+ )
674
+
675
+ else:
676
+ return data_provider.get_data(date=date)
677
+
678
+ def get_ohlcv_data(
679
+ self,
680
+ symbol: str,
681
+ market: str = None,
682
+ time_frame: str = None,
683
+ date: Optional[datetime] = None,
684
+ start_date: Optional[datetime] = None,
685
+ end_date: Optional[datetime] = None,
686
+ window_size: Optional[int] = None,
687
+ pandas: bool = False,
688
+ add_pandas_index: bool = True,
689
+ add_datetime_column: bool = False,
690
+ ):
691
+ """
692
+ Function to get OHLCV data from the data provider.
693
+
694
+ Args:
695
+ symbol (str): The symbol to get OHLCV data for.
696
+ market (str): The market to get OHLCV data for.
697
+ time_frame (str): The time frame to get OHLCV data for.
698
+ date (datetime): The date to get OHLCV data for.
699
+ start_date (datetime): The start date for the OHLCV data.
700
+ end_date (datetime): The end date for the OHLCV data.
701
+ window_size (int): The window size for the OHLCV data.
702
+ pandas (bool): Whether to return the data as a pandas DataFrame.
703
+ add_pandas_index (bool): Whether to add a pandas index to
704
+ the DataFrame if pandas is True.
705
+ add_datetime_column (bool): Whether to add a datetime column
706
+ to the DataFrame if pandas is True.
707
+
708
+ Returns:
709
+ DataFrame: The OHLCV data for the given symbol and market.
710
+ """
711
+ data_provider = self.data_provider_index.get_ohlcv_data_provider(
712
+ symbol=symbol,
713
+ market=market,
714
+ time_frame=time_frame
715
+ )
716
+
717
+ if data_provider is None:
718
+ if market is not None:
719
+ raise OperationalException(
720
+ "No OHLCV data provider found "
721
+ f"for symbol: {symbol} and market: {market}"
722
+ )
723
+ else:
724
+ raise OperationalException(
725
+ f"No OHLCV data provider found for symbol: {symbol}"
726
+ )
727
+
728
+ if self.backtest_mode:
729
+ data = data_provider.get_backtest_data(
730
+ backtest_index_date=date,
731
+ backtest_start_date=start_date,
732
+ backtest_end_date=end_date,
733
+ )
734
+ else:
735
+ data = data_provider.get_data(
736
+ date=date,
737
+ start_date=start_date,
738
+ end_date=end_date,
739
+ )
740
+
741
+ if pandas:
742
+ if isinstance(data, pl.DataFrame):
743
+ return convert_polars_to_pandas(
744
+ data,
745
+ add_index=add_pandas_index,
746
+ add_datetime_column=add_datetime_column
747
+ )
748
+ else:
749
+ return data
750
+
751
+ if isinstance(data, pd.DataFrame):
752
+ # Convert to Polars DataFrame for consistency
753
+ data.index.name = "Datetime"
754
+ data = data.reset_index()
755
+ data = pl.from_pandas(data)
756
+
757
+ return data
758
+
759
+ def get_backtest_data(
760
+ self,
761
+ data_source: DataSource,
762
+ backtest_index_date: datetime = None,
763
+ start_date: datetime = None,
764
+ end_date: datetime = None,
765
+ ):
766
+ """
767
+ Function to get backtest data from the data provider.
768
+
769
+ Args:
770
+ data_source (DataSource): The data source specification that
771
+ matches a data provider.
772
+ backtest_index_date (datetime): The current date of the backtest.
773
+ start_date (datetime): The start date for the data.
774
+ end_date (datetime): The end date for the data.
775
+
776
+ Returns:
777
+ DataFrame: The backtest data for the given symbol and market.
778
+ """
779
+ data_provider = self.data_provider_index.get(data_source=data_source)
780
+
781
+ if data_provider is None:
782
+ self._throw_no_data_provider_exception(data_source.to_dict())
783
+
784
+ return data_provider.get_backtest_data(
785
+ backtest_index_date=backtest_index_date,
786
+ backtest_start_date=start_date,
787
+ backtest_end_date=end_date,
788
+ data_source=data_source
789
+ )
790
+
791
+ def get_vectorized_backtest_data(
792
+ self,
793
+ data_sources: List[DataSource],
794
+ start_date: datetime = None,
795
+ end_date: datetime = None,
796
+ ) -> Dict[str, Any]:
797
+ """
798
+ Function to get vectorized backtest data from the data provider.
799
+
800
+ Args:
801
+ data_sources (List[DataSource]): The data sources to get
802
+ backtest data for.
803
+ start_date (datetime): The start date for the backtest data.
804
+ end_date (datetime): The end date for the backtest data.
805
+
806
+ Returns:
807
+ Dict[str, Any]: The vectorized backtest data for the
808
+ given data sources.
809
+ """
810
+ vectorized_data = {}
811
+
812
+ for data_source in data_sources:
813
+ data_start_date = data_source.create_start_date_data(start_date)
814
+ backtest_data = self.get_backtest_data(
815
+ data_source=data_source,
816
+ start_date=data_start_date,
817
+ end_date=end_date,
818
+ )
819
+ vectorized_data[data_source.get_identifier()] = backtest_data
820
+
821
+ return vectorized_data
822
+
823
+ def _throw_no_data_provider_exception(self, params):
824
+ """
825
+ Raise an exception if no data provider is found for the given params
826
+ """
827
+ non_null_params = {k: v for k, v in params.items() if v is not None}
828
+ if len(non_null_params) == 0:
829
+ raise OperationalException(
830
+ "No data provider found for the given parameters"
831
+ )
832
+
833
+ params = ", ".join(
834
+ [f"{k}: {v}" for k, v in non_null_params.items()]
835
+ )
836
+
837
+ raise OperationalException(
838
+ f"No data provider found for the given parameters: {params}"
839
+ )
840
+
841
+ def register_data_provider(
842
+ self, data_source: DataSource, data_provider: DataProvider
843
+ ) -> DataProvider:
844
+ """
845
+ Function to directly register a data provider for a given data source.
846
+
847
+ This method will not check if the data provider supports the
848
+ data source. It will directly register the data provider in the index.
849
+
850
+ Args:
851
+ data_source (DataSource): The data source to register the
852
+ data provider for.
853
+ data_provider (DataProvider): The data provider to register.
854
+
855
+ Returns:
856
+ DataProvider: The registered data provider.
857
+ """
858
+ data_provider = data_provider.copy(data_source)
859
+ self.data_provider_index.data_providers_lookup[data_source] = \
860
+ data_provider
861
+ return data_provider
862
+
863
+ def add_data_provider(
864
+ self, data_provider: DataProvider, priority: int = 3
865
+ ):
866
+ """
867
+ Add a data provider to the service.
868
+
869
+ Args:
870
+ data_provider (DataProvider): The data provider to add.
871
+ priority (int): The priority of the data provider.
872
+
873
+ Returns:
874
+ None
875
+ """
876
+ data_provider.priority = priority
877
+ data_provider.config = self.configuration_service.get_config()
878
+ self.data_provider_index.add(data_provider)
879
+
880
+ def index_data_providers(self, data_sources: List[DataSource]):
881
+ """
882
+ Index the data providers in the service.
883
+ This will create a fast lookup index for the data providers
884
+ based on the given parameters.
885
+
886
+ Args:
887
+ data_sources (List[DataSource]): The data sources to index.
888
+
889
+ Returns:
890
+ None
891
+ """
892
+
893
+ for data_source in data_sources:
894
+ self.data_provider_index.register(data_source)
895
+ logger.debug(
896
+ "Registered data provider for data source: {data_source}"
897
+ )
898
+
899
+ def index_backtest_data_providers(
900
+ self,
901
+ data_sources: List[DataSource],
902
+ backtest_date_range: BacktestDateRange,
903
+ show_progress: bool = True
904
+ ):
905
+ """
906
+ Index the data providers in the service.
907
+ This will create a fast lookup index for the data providers
908
+ based on the given parameters.
909
+
910
+ Args:
911
+ data_sources (List[DataSource]): The data sources to index.
912
+ backtest_date_range (BacktestDateRange): The date range for the
913
+ backtest data providers.
914
+ show_progress (bool): Whether to show progress while indexing
915
+ the data providers.
916
+
917
+ Returns:
918
+ None
919
+ """
920
+
921
+ # Filter out duplicate data_sources
922
+ unique_data_sources = set(data_sources)
923
+
924
+ if show_progress:
925
+
926
+ for data_source in tqdm(
927
+ unique_data_sources,
928
+ desc="Registering backtest data providers for data sources",
929
+ colour="green"
930
+ ):
931
+ self.data_provider_index.register_backtest_data_source(
932
+ data_source, backtest_date_range
933
+ )
934
+ logger.debug(
935
+ "Registered backtest "
936
+ f"data provider for data source: {data_source}"
937
+ )
938
+ else:
939
+ for data_source in unique_data_sources:
940
+ self.data_provider_index.register_backtest_data_source(
941
+ data_source, backtest_date_range
942
+ )
943
+ logger.debug(
944
+ "Registered backtest "
945
+ f"data provider for data source: {data_source}"
946
+ )
947
+
948
+ self.backtest_mode = True
949
+
950
+ def prepare_backtest_data(
951
+ self,
952
+ backtest_date_range: BacktestDateRange,
953
+ show_progress: bool = True
954
+ ):
955
+ """
956
+ Prepare the backtest data for all registered data providers.
957
+
958
+ Args:
959
+ backtest_date_range (BacktestDateRange): The date range for the
960
+ backtest data.
961
+ show_progress (bool): Whether to show progress while preparing
962
+ the backtest data.
963
+
964
+ Raises:
965
+ OperationalException: If no data providers are registered.
966
+
967
+ Returns:
968
+ None
969
+ """
970
+
971
+ if len(self.data_provider_index.data_providers_lookup) == 0:
972
+ raise OperationalException(
973
+ "No data providers registered. "
974
+ "Please register at least one data provider before preparing "
975
+ "backtest data."
976
+ )
977
+
978
+ logger.info(
979
+ "Preparing backtest data for all registered data providers"
980
+ )
981
+
982
+ if show_progress:
983
+ for data_source, data_provider in tqdm(
984
+ self.data_provider_index.get_all(),
985
+ desc="Preparing backtest data",
986
+ colour="green"
987
+ ):
988
+ try:
989
+ data_provider.prepare_backtest_data(
990
+ backtest_start_date=backtest_date_range.start_date,
991
+ backtest_end_date=backtest_date_range.end_date
992
+ )
993
+ except Exception as e:
994
+ logger.error(
995
+ f"Error preparing backtest data for {data_source}: {e}"
996
+ )
997
+ else:
998
+ for data_source, data_provider \
999
+ in self.data_provider_index.get_all():
1000
+
1001
+ try:
1002
+ data_provider.prepare_backtest_data(
1003
+ backtest_start_date=backtest_date_range.start_date,
1004
+ backtest_end_date=backtest_date_range.end_date
1005
+ )
1006
+ except Exception as e:
1007
+ logger.error(
1008
+ f"Error preparing backtest data for {data_source}: {e}"
1009
+ )
1010
+
1011
+ def get_data_files(self):
1012
+ """
1013
+ Function to get the data files for the market data sources.
1014
+
1015
+ Returns:
1016
+ List[str]: A list of file paths for the data files.
1017
+ """
1018
+ data_files = []
1019
+
1020
+ for market_data_source in self.data_provider_index.data_providers:
1021
+ if hasattr(market_data_source, 'file_path') and \
1022
+ market_data_source.file_path is not None:
1023
+ data_files.append(market_data_source.file_path)
1024
+
1025
+ return data_files
1026
+
1027
+ def get_all_registered_data_providers(self) -> List[DataProvider]:
1028
+ """
1029
+ Function to get all registered data providers.
1030
+
1031
+ Returns:
1032
+ List[DataProvider]: A list of all registered data providers.
1033
+ """
1034
+ return self.data_provider_index.get_all()
1035
+
1036
+ def reset(self):
1037
+ """
1038
+ Function to reset all the data providers and the data provider
1039
+ lookup index.
1040
+ """
1041
+ self.data_provider_index.reset()
1042
+ self.backtest_mode = False
1043
+
1044
+ def copy(self):
1045
+ """
1046
+ Create a copy of the DataProviderService.
1047
+
1048
+ Returns:
1049
+ DataProviderService: A copy of the current service.
1050
+ """
1051
+ new_service = DataProviderService(
1052
+ configuration_service=self.configuration_service,
1053
+ market_credential_service=self.market_credential_service,
1054
+ default_data_providers=self.default_data_providers
1055
+ )
1056
+ new_service.data_provider_index = self.data_provider_index
1057
+ new_service.backtest_mode = self.backtest_mode
1058
+ return new_service