investing-algorithm-framework 7.19.14__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.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -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/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,568 @@
1
+ from typing import List, Union
2
+ from datetime import datetime, timezone, timedelta
3
+
4
+ import polars as pl
5
+
6
+ from investing_algorithm_framework.domain import DataProvider, \
7
+ OperationalException, DataSource, DataType, TimeFrame, \
8
+ convert_polars_to_pandas
9
+
10
+
11
+ class CSVOHLCVDataProvider(DataProvider):
12
+ """
13
+ Implementation of Data Provider for OHLCV data. OHLCV data
14
+ will be loaded from a CSV file. The CSV file should contain
15
+ the following columns: Datetime, Open, High, Low, Close, Volume.
16
+ The Datetime column should be in UTC timezone and in milliseconds.
17
+ The data will be loaded into a Polars DataFrame and will be kept in memory.
18
+
19
+ Attributes:
20
+ data_type (DataType): The type of data provided by this provider,
21
+ which is OHLCV.
22
+ data_provider_identifier (str): Identifier for the CSV OHLCV data
23
+ provider.
24
+ _start_date_data_source (datetime): The start date of the data
25
+ source, determined from the first row of the data.
26
+ _end_date_data_source (datetime): The end date of the data
27
+ source, determined from the last row of the data.
28
+ data (polars.DataFrame): The OHLCV data loaded from the CSV file.
29
+ """
30
+ data_type = DataType.OHLCV
31
+ data_provider_identifier = "csv_ohlcv_data_provider"
32
+
33
+ def __init__(
34
+ self,
35
+ storage_path: str,
36
+ symbol: str,
37
+ time_frame: str,
38
+ market: str,
39
+ window_size=None,
40
+ data_provider_identifier: str = None,
41
+ pandas: bool = False,
42
+ ):
43
+ """
44
+ Initialize the CSV Data Provider.
45
+
46
+ Args:
47
+ storage_path (str): Path to the CSV file.
48
+ symbol (str): The symbol for which the data is provided.
49
+ time_frame (str): The time frame for the data.
50
+ market (str, optional): The market for the data. Defaults to None.
51
+ window_size (int, optional): The window size for the data.
52
+ Defaults to None.
53
+ """
54
+ if data_provider_identifier is None:
55
+ data_provider_identifier = self.data_provider_identifier
56
+ super().__init__(
57
+ symbol=symbol,
58
+ market=market,
59
+ time_frame=time_frame,
60
+ window_size=window_size,
61
+ storage_path=storage_path,
62
+ data_provider_identifier=data_provider_identifier,
63
+ data_type=DataType.OHLCV.value
64
+ )
65
+ self._start_date_data_source = None
66
+ self._end_date_data_source = None
67
+ self._columns = ["Datetime", "Open", "High", "Low", "Close", "Volume"]
68
+ self.window_cache = {}
69
+ self._load_data(self.storage_path)
70
+ self.pandas = pandas
71
+ self.number_of_missing_data_points = 0
72
+ self.missing_data_point_dates: List[datetime] = []
73
+
74
+ def has_data(
75
+ self,
76
+ data_source: DataSource,
77
+ start_date: datetime = None,
78
+ end_date: datetime = None
79
+ ) -> bool:
80
+ """
81
+ Implementation of the has_data method to check if
82
+ the data provider has data for the given data source.
83
+
84
+ Args:
85
+ data_source (DataSource): The data source to check.
86
+ start_date (datetime, optional): The start date for the data.
87
+ Defaults to None.
88
+ end_date (datetime, optional): The end date for the data.
89
+ Defaults to None.
90
+
91
+ Returns:
92
+ bool: True if the data provider has data for the given data source,
93
+ False otherwise.
94
+ """
95
+
96
+ if start_date is None and end_date is None:
97
+ return False
98
+
99
+ if DataType.OHLCV.equals(data_source.data_type) and \
100
+ data_source.symbol == self.symbol and \
101
+ data_source.time_frame.equals(self.time_frame) and \
102
+ data_source.market == self.market:
103
+
104
+ if end_date > self._end_date_data_source:
105
+ return False
106
+
107
+ if data_source.window_size is not None:
108
+ minutes = TimeFrame.from_value(
109
+ data_source.time_frame
110
+ ).amount_of_minutes * data_source.window_size
111
+ required_start_date = end_date - timedelta(
112
+ minutes=minutes
113
+ )
114
+
115
+ if required_start_date < self._start_date_data_source:
116
+ return False
117
+ else:
118
+ required_start_date = start_date
119
+ if required_start_date < self._start_date_data_source:
120
+ return False
121
+
122
+ return True
123
+
124
+ return False
125
+
126
+ def get_data(
127
+ self,
128
+ date: datetime = None,
129
+ start_date: datetime = None,
130
+ end_date: datetime = None,
131
+ save: bool = False,
132
+ ):
133
+ """
134
+ Fetches OHLCV data for a given symbol and date range.
135
+ If no date range is provided, it returns the entire dataset.
136
+
137
+ Args:
138
+ date (datetime, optional): A specific date to fetch data for.
139
+ Defaults to None.
140
+ start_date (datetime, optional): The start date for the data.
141
+ Defaults to None.
142
+ end_date (datetime, optional): The end date for the data.
143
+ Defaults to None.
144
+ save (bool, optional): Whether to save the data to a file.
145
+
146
+ Returns:
147
+ polars.DataFrame: A DataFrame containing the OHLCV data for the
148
+ specified symbol and date range.
149
+ """
150
+ windows_size = self.window_size
151
+
152
+ if start_date is None and end_date is None:
153
+ end_date = datetime.now(tz=timezone.utc)
154
+ time_frame = TimeFrame.from_value(self.time_frame)
155
+ start_date = end_date - timedelta(
156
+ minutes=time_frame.amount_of_minutes() * windows_size
157
+ )
158
+ elif start_date is None and end_date is not None:
159
+ start_date = end_date - timedelta(
160
+ minutes=TimeFrame.from_value(
161
+ self.time_frame
162
+ ).amount_of_minutes * windows_size
163
+ )
164
+ df = self.data
165
+ df = df.filter(
166
+ (df['Datetime'] >= start_date) & (df['Datetime'] <= end_date)
167
+ )
168
+ return df
169
+
170
+ if start_date is not None:
171
+ end_date = start_date + timedelta(
172
+ minutes=TimeFrame.from_value(self.time_frame)
173
+ .amount_of_minutes * windows_size
174
+ )
175
+
176
+ if start_date < self._start_date_data_source:
177
+ return pl.DataFrame()
178
+
179
+ if start_date > self._end_date_data_source:
180
+ return pl.DataFrame()
181
+
182
+ df = self.data
183
+ df = df.filter(
184
+ (df['Datetime'] >= start_date) & (df['Datetime'] <= end_date)
185
+ )
186
+ return df
187
+
188
+ if end_date is not None:
189
+ start_date = end_date - timedelta(
190
+ minutes=TimeFrame.from_value(
191
+ self.time_frame
192
+ ).amount_of_minutes * windows_size
193
+ )
194
+
195
+ if end_date < self._start_date_data_source:
196
+ return pl.DataFrame()
197
+
198
+ if end_date > self._end_date_data_source:
199
+ return pl.DataFrame()
200
+
201
+ df = self.data
202
+ df = df.filter(
203
+ (df['Datetime'] >= start_date) & (df['Datetime'] <= end_date)
204
+ )
205
+ return df
206
+
207
+ return self.data
208
+
209
+ def prepare_backtest_data(
210
+ self,
211
+ backtest_start_date,
212
+ backtest_end_date
213
+ ) -> None:
214
+ """
215
+ Prepares backtest data for a given symbol and date range.
216
+
217
+ Args:
218
+ backtest_start_date (datetime): The start date for the
219
+ backtest data.
220
+ backtest_end_date (datetime): The end date for the
221
+ backtest data.
222
+
223
+ Raises:
224
+ OperationalException: If the backtest start date is before the
225
+ start date of the data source or if the backtest end date is
226
+ after the end date of the data source.
227
+
228
+ Returns:
229
+ None
230
+ """
231
+
232
+ if backtest_start_date < self._start_date_data_source:
233
+ raise OperationalException(
234
+ f"Backtest start date {backtest_start_date} is before the "
235
+ f"start date {self._start_date_data_source}"
236
+ )
237
+
238
+ if backtest_end_date > self._end_date_data_source:
239
+ raise OperationalException(
240
+ f"Backtest end date {backtest_end_date} is after the "
241
+ f"end date {self._end_date_data_source}"
242
+ )
243
+
244
+ # There must be at least backtest_start_date - window_size * time_frame
245
+ # data available to create a sliding window.
246
+ required_start_date = backtest_start_date - \
247
+ timedelta(
248
+ minutes=TimeFrame.from_value(self.time_frame)
249
+ .amount_of_minutes * self.window_size
250
+ )
251
+
252
+ # Create cache with sliding windows
253
+ self._precompute_sliding_windows(
254
+ window_size=self.window_size,
255
+ start_date=backtest_start_date,
256
+ end_date=backtest_end_date
257
+ )
258
+
259
+ if required_start_date < self._start_date_data_source:
260
+ self.number_of_missing_data_points = (
261
+ self._start_date_data_source - required_start_date
262
+ ).total_seconds() / (
263
+ TimeFrame.from_value(self.time_frame).amount_of_minutes * 60
264
+ )
265
+
266
+ n_min = TimeFrame.from_value(self.time_frame).amount_of_minutes
267
+
268
+ # Assume self.data is a Polars DataFrame with a "Datetime" column
269
+ expected_dates = pl.datetime_range(
270
+ start=required_start_date,
271
+ end=backtest_end_date,
272
+ interval=f"{n_min}m",
273
+ eager=True
274
+ ).to_list()
275
+
276
+ actual_dates = self.data["Datetime"].to_list()
277
+
278
+ # Find missing dates
279
+ self.missing_data_point_dates = sorted(
280
+ set(expected_dates) - set(actual_dates)
281
+ )
282
+
283
+ def get_backtest_data(
284
+ self,
285
+ backtest_index_date: datetime,
286
+ backtest_start_date: datetime = None,
287
+ backtest_end_date: datetime = None,
288
+ data_source: DataSource = None
289
+ ) -> None:
290
+ """
291
+ Fetches backtest data for a given datasource
292
+
293
+ Args:
294
+ backtest_index_date (datetime): The date for which to fetch
295
+ backtest data.
296
+ backtest_start_date (datetime): The start date for the
297
+ backtest data.
298
+ backtest_end_date (datetime): The end date for the
299
+ backtest data.
300
+ data_source (Optional[DataSource]): The data source specification
301
+ that matches a data provider.
302
+
303
+ Raises:
304
+ OperationalException: If the requested backtest date range
305
+ is outside the available data range.
306
+
307
+ Returns:
308
+ pl.DataFrame: The backtest data for the given datasource.
309
+ """
310
+ if backtest_start_date is not None and \
311
+ backtest_end_date is not None:
312
+
313
+ if backtest_start_date < self._start_date_data_source:
314
+
315
+ if data_source is not None:
316
+ raise OperationalException(
317
+ f"Request data date {backtest_end_date} "
318
+ f"is after the range of "
319
+ f"the available data "
320
+ f"{self._start_date_data_source} "
321
+ f"- {self._end_date_data_source}."
322
+ f" for data source {data_source.identifier}."
323
+ )
324
+
325
+ raise OperationalException(
326
+ f"Request data date {backtest_start_date} "
327
+ f"is before the range of "
328
+ f"the available data "
329
+ f"{self._start_date_data_source} "
330
+ f"- {self._end_date_data_source}."
331
+ )
332
+
333
+ if backtest_end_date > self._end_date_data_source:
334
+
335
+ if data_source is not None:
336
+ raise OperationalException(
337
+ f"Request data date {backtest_end_date} "
338
+ f"is after the range of "
339
+ f"the available data "
340
+ f"{self._start_date_data_source} "
341
+ f"- {self._end_date_data_source}."
342
+ f" for data source {data_source.identifier}."
343
+ )
344
+
345
+ raise OperationalException(
346
+ f"Request data date {backtest_end_date} "
347
+ f"is after the range of "
348
+ f"the available data "
349
+ f"{self._start_date_data_source} "
350
+ f"- {self._end_date_data_source}."
351
+ )
352
+
353
+ data = self.data.filter(
354
+ (pl.col("Datetime") >= backtest_start_date) &
355
+ (pl.col("Datetime") <= backtest_end_date)
356
+ )
357
+ else:
358
+ try:
359
+ data = self.window_cache[backtest_index_date]
360
+ except KeyError:
361
+
362
+ try:
363
+ # Return the key in the cache that is closest to the
364
+ # backtest_index_date but not after it.
365
+ closest_key = min(
366
+ [k for k in self.window_cache.keys()
367
+ if k >= backtest_index_date]
368
+ )
369
+ data = self.window_cache[closest_key]
370
+ except ValueError:
371
+
372
+ if data_source is not None:
373
+ raise OperationalException(
374
+ "No data available for the "
375
+ f"date: {backtest_index_date} "
376
+ "within the prepared backtest data "
377
+ f"for data source {data_source.identifier}."
378
+ )
379
+
380
+ raise OperationalException(
381
+ "No data available for the "
382
+ f"date: {backtest_index_date} "
383
+ "within the prepared backtest data."
384
+ )
385
+
386
+ if self.pandas:
387
+ data = convert_polars_to_pandas(data)
388
+
389
+ return data
390
+
391
+ def _load_data(self, storage_path):
392
+ """
393
+ Load OHLCV data from a CSV file into a Polars DataFrame.
394
+ The CSV file should contain the following columns:
395
+
396
+ Datetime, Open, High, Low, Close, Volume.
397
+
398
+ The Datetime column should be in UTC timezone and in milliseconds.
399
+
400
+ Args:
401
+ storage_path (str): The path to the CSV file containing OHLCV data.
402
+
403
+ Raises:
404
+ OperationalException: If the CSV file does not contain all
405
+ required OHLCV columns.
406
+
407
+ Returns:
408
+ None
409
+ """
410
+ df = pl.read_csv(storage_path)
411
+
412
+ # Check if all column names are in the csv file
413
+ if not all(column in df.columns for column in self._columns):
414
+ # Identify missing columns
415
+ missing_columns = [column for column in self._columns if
416
+ column not in df.columns]
417
+ raise OperationalException(
418
+ f"Csv file {storage_path} does not contain "
419
+ f"all required ohlcv columns. "
420
+ f"Missing columns: {missing_columns}"
421
+ )
422
+
423
+ self.data = pl.read_csv(
424
+ storage_path,
425
+ schema_overrides={"Datetime": pl.Datetime},
426
+ low_memory=True
427
+ ).with_columns(
428
+ pl.col("Datetime").cast(
429
+ pl.Datetime(time_unit="ms", time_zone="UTC")
430
+ )
431
+ )
432
+
433
+ first_row = self.data.head(1)
434
+ last_row = self.data.tail(1)
435
+ self._start_date_data_source = first_row["Datetime"][0]
436
+ self._end_date_data_source = last_row["Datetime"][0]
437
+
438
+ def _precompute_sliding_windows(
439
+ self,
440
+ window_size: int,
441
+ start_date: datetime,
442
+ end_date: datetime
443
+ ) -> None:
444
+ """
445
+ Precompute all sliding windows for fast retrieval in backtest mode.
446
+
447
+ A sliding window is calculated as a subset of the data. It will
448
+ take for each timestamp in the data a window of size `window_size`
449
+ and stores it in a cache with the last timestamp of the window.
450
+
451
+ So if the window size is 200, the first window will be
452
+ the first 200 rows of the data, the second window will be
453
+ the rows 1 to 200, the third window will be the rows
454
+ 2 to 201, and so on until the last window which will be
455
+ the last 200 rows of the data.
456
+
457
+ Args:
458
+ window_size (int): The size of the sliding window to precompute.
459
+ start_date (datetime, optional): The start date for the sliding
460
+ windows.
461
+ end_date (datetime, optional): The end date for the sliding
462
+ windows.
463
+
464
+ Returns:
465
+ None
466
+ """
467
+ self.window_cache = {}
468
+ timestamps = self.data["Datetime"].to_list()
469
+
470
+ # Only select the entries after the start date
471
+ timestamps = [
472
+ ts for ts in timestamps
473
+ if start_date <= ts <= end_date
474
+ ]
475
+
476
+ # Create sliding windows of size <window_size> for each timestamp
477
+ # in the data with the given the time frame and window size
478
+ for timestamp in timestamps:
479
+ # Use timestamp as key
480
+ self.window_cache[timestamp] = self.data.filter(
481
+ (self.data["Datetime"] <= timestamp) &
482
+ (self.data["Datetime"] >= timestamp - timedelta(
483
+ minutes=self.time_frame.amount_of_minutes * window_size
484
+ ))
485
+ )
486
+
487
+ def copy(self, data_source: DataSource) -> "DataProvider":
488
+ """
489
+ Create a copy of the data provider with the given data source.
490
+
491
+ Args:
492
+ data_source (DataSource): The data source to copy.
493
+
494
+ Returns:
495
+ DataProvider: A new instance of the data provider with the
496
+ specified data source.
497
+ """
498
+
499
+ storage_path = data_source.storage_path
500
+
501
+ if storage_path is None:
502
+ storage_path = self.storage_path
503
+
504
+ return CSVOHLCVDataProvider(
505
+ storage_path=storage_path,
506
+ symbol=data_source.symbol,
507
+ time_frame=data_source.time_frame,
508
+ market=data_source.market,
509
+ window_size=data_source.window_size,
510
+ data_provider_identifier=self.data_provider_identifier
511
+ )
512
+
513
+ def get_number_of_data_points(
514
+ self,
515
+ start_date: datetime,
516
+ end_date: datetime
517
+ ) -> int:
518
+
519
+ """
520
+ Returns the number of data points available between the given
521
+ start and end dates.
522
+
523
+ Args:
524
+ start_date (datetime): The start date for checking missing data.
525
+ end_date (datetime): The end date for checking missing data.
526
+
527
+ Returns:
528
+ int: The number of available data points between the given
529
+ start and end dates.
530
+ """
531
+ available_dates = [
532
+ date for date in self.data["Datetime"].to_list()
533
+ if start_date <= date <= end_date
534
+ ]
535
+ return len(available_dates)
536
+
537
+ def get_missing_data_dates(
538
+ self,
539
+ start_date: datetime,
540
+ end_date: datetime,
541
+ ) -> List[datetime]:
542
+ """
543
+ Returns a list of dates for which data is missing between the
544
+ given start and end dates.
545
+
546
+ Args:
547
+ start_date (datetime): The start date for checking missing data.
548
+ end_date (datetime): The end date for checking missing data.
549
+
550
+ Returns:
551
+ List[datetime]: A list of dates for which data is missing
552
+ between the given start and end dates.
553
+ """
554
+ missing_dates = [
555
+ date for date in self.missing_data_point_dates
556
+ if start_date <= date <= end_date
557
+ ]
558
+ return missing_dates
559
+
560
+ def get_data_source_file_path(self) -> Union[str, None]:
561
+ """
562
+ Get the file path of the data source if stored in local storage.
563
+
564
+ Returns:
565
+ Union[str, None]: The file path of the data source if stored
566
+ locally, otherwise None.
567
+ """
568
+ return self.storage_path