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