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,349 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ import webbrowser
5
+ from dataclasses import dataclass
6
+ from dataclasses import field
7
+ from typing import List, Union
8
+
9
+ import pandas as pd
10
+ from IPython import get_ipython
11
+ from IPython.display import display, HTML
12
+ from jinja2 import Environment, FileSystemLoader
13
+
14
+ from investing_algorithm_framework.domain import TimeFrame, Backtest, \
15
+ OperationalException, BacktestDateRange
16
+ from .charts import get_equity_curve_with_drawdown_chart, \
17
+ get_rolling_sharpe_ratio_chart, get_monthly_returns_heatmap_chart, \
18
+ get_yearly_returns_bar_chart, get_ohlcv_data_completeness_chart
19
+ from .tables import create_html_time_metrics_table, \
20
+ create_html_trade_metrics_table, create_html_key_metrics_table, \
21
+ create_html_trades_table
22
+
23
+ logger = logging.getLogger("investing_algorithm_framework")
24
+
25
+
26
+ def get_symbol_from_file_name(file_name: str) -> str:
27
+ """
28
+ Extract the symbol from the file name.
29
+
30
+ Args:
31
+ file_name (str): The file name from which to extract the symbol.
32
+
33
+ Returns:
34
+ str: The extracted symbol.
35
+ """
36
+ # Assuming the file name format is "symbol_timeframe.csv"
37
+ return file_name.split('_')[0].upper()
38
+
39
+ def get_market_from_file_name(file_name: str) -> str:
40
+ """
41
+ Extract the market from the file name.
42
+
43
+ Args:
44
+ file_name (str): The file name from which to extract the market.
45
+
46
+ Returns:
47
+ str: The extracted market.
48
+ """
49
+ # Assuming the file name format is "symbol_market_timeframe.csv"
50
+ parts = file_name.split('_')
51
+ if len(parts) < 2:
52
+ raise ValueError("File name does not contain a valid market.")
53
+ return parts[1].upper()
54
+
55
+
56
+ def get_time_frame_from_file_name(file_name: str) -> TimeFrame:
57
+ """
58
+ Extract the time frame from the file name.
59
+
60
+ Args:
61
+ file_name (str): The file name from which to extract the time frame.
62
+
63
+ Returns:
64
+ TimeFrame: The extracted time frame.
65
+ """
66
+ parts = file_name.split('_')
67
+
68
+ if len(parts) < 3:
69
+ raise ValueError(
70
+ "File name does not contain a valid time frame."
71
+ )
72
+ time_frame_str = parts[3]
73
+
74
+ try:
75
+ return TimeFrame.from_string(time_frame_str)
76
+ except ValueError:
77
+ raise ValueError(
78
+ f"Could not extract time frame from file name: {file_path}. "
79
+ f"Expected format 'OHLCV_<SYMBOL>_<MARKET>_<TIME_FRAME>_<START_DATE>_<END_DATE>.csv', "
80
+ f"got '{time_frame_str}'."
81
+ )
82
+
83
+
84
+ @dataclass
85
+ class BacktestReport:
86
+ """
87
+ A class to represent a backtest report. The backtest report contains
88
+ the results of a backtest, including metrics and an HTML report. Also,
89
+ it stores the path to the used strategy directory, which can be used to
90
+ load the strategy code later.
91
+
92
+ Attributes:
93
+ html_report (str): The HTML report content.
94
+ html_report_path (str): The file path where the HTML report is saved.
95
+ metrics (dict): A dictionary containing various metrics from the backtest.
96
+ results (BacktestResult): An instance of BacktestResult containing
97
+ the results of the backtest.
98
+ risk_free_rate (float): The risk-free rate used in the backtest.
99
+ strategy_path (str): The path to the strategy directory used in the
100
+ backtest.
101
+ """
102
+ backtest: Backtest = None
103
+ html_report: str = None
104
+ html_report_path: str = None
105
+
106
+ def show(
107
+ self,
108
+ backtest_date_range: BacktestDateRange,
109
+ browser: bool = False
110
+ ):
111
+ """
112
+ Display the HTML report in a Jupyter notebook cell.
113
+ """
114
+
115
+ if not self.html_report:
116
+ # If the HTML report is not created, create it
117
+ self._create_html_report(backtest_date_range)
118
+
119
+ # Save the html report to a tmp location
120
+ path = "/tmp/backtest_report.html"
121
+ with open(path, "w") as html_file:
122
+ html_file.write(self.html_report)
123
+
124
+ if browser:
125
+ webbrowser.open(f"file://{path}")
126
+
127
+ def in_jupyter_notebook():
128
+ try:
129
+ shell = get_ipython().__class__.__name__
130
+ return shell == 'ZMQInteractiveShell'
131
+ except (NameError, ImportError):
132
+ return False
133
+
134
+ if in_jupyter_notebook():
135
+ display(HTML(self.html_report))
136
+ else:
137
+ webbrowser.open(f"file://{path}")
138
+
139
+ def _create_html_report(self, backtest_date_range: BacktestDateRange):
140
+ """
141
+ Create an HTML report from the backtest metrics and results.
142
+
143
+ This method generates various charts and tables from the backtest
144
+ metrics and results, and renders them into an HTML template using
145
+ Jinja2.
146
+
147
+ Raises:
148
+ OperationalException: If no backtests are available to
149
+ create a report.
150
+
151
+ Returns:
152
+ None
153
+ """
154
+ # Get the first backtest
155
+ if not self.backtest:
156
+ raise OperationalException(
157
+ "No backtest available to create a report."
158
+ )
159
+
160
+
161
+ metrics = self.backtest.get_backtest_metrics(backtest_date_range)
162
+ run = self.backtest.get_backtest_run(backtest_date_range)
163
+ # Create plots
164
+ equity_with_drawdown_fig = get_equity_curve_with_drawdown_chart(
165
+ metrics.equity_curve, metrics.drawdown_series
166
+ )
167
+ equity_with_drawdown_plot_html = equity_with_drawdown_fig.to_html(
168
+ full_html=False, include_plotlyjs='cdn',
169
+ config={'responsive': True}, default_width="90%"
170
+ )
171
+ rolling_sharpe_ratio_fig = get_rolling_sharpe_ratio_chart(
172
+ metrics.rolling_sharpe_ratio
173
+ )
174
+ rolling_sharpe_ratio_plot_html = rolling_sharpe_ratio_fig.to_html(
175
+ full_html=False, include_plotlyjs='cdn',
176
+ config={'responsive': True}, default_width="90%"
177
+ )
178
+ monthly_returns_heatmap_fig = get_monthly_returns_heatmap_chart(
179
+ metrics.monthly_returns
180
+ )
181
+ monthly_returns_heatmap_html = monthly_returns_heatmap_fig.to_html(
182
+ full_html=False, include_plotlyjs='cdn',
183
+ config={'responsive': True}
184
+ )
185
+ yearly_returns_histogram_fig = get_yearly_returns_bar_chart(
186
+ metrics.yearly_returns
187
+ )
188
+ yearly_returns_histogram_html = yearly_returns_histogram_fig.to_html(
189
+ full_html=False, include_plotlyjs='cdn',
190
+ config={'responsive': True}
191
+ )
192
+
193
+ # Create OHLCV data completeness charts
194
+ data_files = []
195
+ ohlcv_data_completeness_charts_html = ""
196
+
197
+ for file in data_files:
198
+ try:
199
+ if file.endswith('.csv'):
200
+ df = pd.read_csv(file, parse_dates=['Datetime'])
201
+ file_name = os.path.basename(file)
202
+ symbol = get_symbol_from_file_name(file_name)
203
+ market = get_market_from_file_name(file_name)
204
+ time_frame = get_time_frame_from_file_name(file_name)
205
+ title = f"OHLCV Data Completeness for {market} - {symbol} - {time_frame.value}"
206
+ ohlcv_data_completeness_chart_html = \
207
+ get_ohlcv_data_completeness_chart(
208
+ df,
209
+ timeframe=time_frame.value,
210
+ windowsize=200,
211
+ title=title
212
+ )
213
+
214
+ ohlcv_data_completeness_charts_html += (
215
+ '<div class="ohlcv-data-completeness-chart">'
216
+ f'{ohlcv_data_completeness_chart_html}'
217
+ '</div>'
218
+ )
219
+
220
+ except Exception as e:
221
+ logger.warning(
222
+ "Error creating OHLCV data completeness " +
223
+ f"chart for {file}: {e}"
224
+ )
225
+ continue
226
+
227
+ # Create HTML tables
228
+ key_metrics_table_html = create_html_key_metrics_table(
229
+ metrics, run
230
+ )
231
+ trades_metrics_table_html = create_html_trade_metrics_table(
232
+ metrics, run
233
+ )
234
+ time_metrics_table_html = create_html_time_metrics_table(
235
+ metrics, run
236
+ )
237
+ trades_table_html = create_html_trades_table(run)
238
+
239
+ # Jinja2 environment setup
240
+ template_dir = os.path.join(os.path.dirname(__file__), 'templates')
241
+ env = Environment(loader=FileSystemLoader(template_dir))
242
+ template = env.get_template('report_template.html.j2')
243
+
244
+ # Render template with variables
245
+ html_rendered = template.render(
246
+ report=run,
247
+ equity_with_drawdown_plot_html=equity_with_drawdown_plot_html,
248
+ rolling_sharpe_ratio_plot_html=rolling_sharpe_ratio_plot_html,
249
+ monthly_returns_heatmap_html=monthly_returns_heatmap_html,
250
+ yearly_returns_histogram_html=yearly_returns_histogram_html,
251
+ key_metrics_table_html=key_metrics_table_html,
252
+ trades_metrics_table_html=trades_metrics_table_html,
253
+ time_metrics_table_html=time_metrics_table_html,
254
+ trades_table_html=trades_table_html,
255
+ data_completeness_charts_html=ohlcv_data_completeness_charts_html
256
+ )
257
+ self.html_report = html_rendered
258
+
259
+ @staticmethod
260
+ def _load_backtest(backtest_path: str) -> Backtest:
261
+ """
262
+ Load the backtest from a give path
263
+ and return an instance of Backtest.
264
+
265
+ Args:
266
+ backtest_path (str): The path to the backtest directory.
267
+
268
+ Returns:
269
+ Backtest: An instance of Backtest loaded from the
270
+ backtest directory.
271
+ """
272
+ return Backtest.open(backtest_path)
273
+
274
+ @staticmethod
275
+ def _is_backtest(backtest_path: Union[str, Path]) -> bool:
276
+ """
277
+ Check if the given path is a valid backtest report directory.
278
+
279
+ Args:
280
+ backtest_path (Union[str, Path]): The path to check.
281
+
282
+ Returns:
283
+ bool: True if the path is a valid backtest report directory,
284
+ False otherwise.
285
+ """
286
+ return (
287
+ os.path.exists(backtest_path) and
288
+ os.path.isdir(backtest_path) and
289
+ os.path.isfile(os.path.join(backtest_path, "results.json"))
290
+ and os.path.isfile(os.path.join(backtest_path, "metrics.json"))
291
+ )
292
+
293
+ @staticmethod
294
+ def open(
295
+ backtests: List[Backtest] = [],
296
+ directory_path=None
297
+ ) -> "BacktestReport":
298
+ """
299
+ Open the backtest report from a file.
300
+
301
+ Args:
302
+ backtests (List[Backtest], optional): A list of Backtest instances
303
+ to include in the report. If provided, it will use these
304
+ backtests as part of the report.
305
+ directory_path (str, optional): The directory path from
306
+ which the reports will be loaded.
307
+
308
+ Returns:
309
+ BacktestReport: An instance of BacktestReport loaded from the file.
310
+
311
+ Raises:
312
+ OperationalException: If the backtest path is not a valid backtest
313
+ report directory.
314
+ """
315
+ loaded_backtests = []
316
+
317
+ if directory_path is not None:
318
+ # Check if the directory is a valid backtest report directory
319
+ if BacktestReport._is_backtest(directory_path):
320
+ backtests.append(
321
+ Backtest.open(directory_path)
322
+ )
323
+ else:
324
+ # Loop over all subdirectories
325
+ for root, dirs, _ in os.walk(directory_path):
326
+ for dir_name in dirs:
327
+ subdir_path = os.path.join(root, dir_name)
328
+ if BacktestReport._is_backtest(subdir_path):
329
+ loaded_backtests.append(
330
+ Backtest.open(directory_path=subdir_path)
331
+ )
332
+
333
+ if len(backtests) > 0:
334
+
335
+ for backtest in backtests:
336
+ if not isinstance(backtest, Backtest):
337
+ raise OperationalException(
338
+ "The provided backtest is not a valid Backtest instance."
339
+ )
340
+
341
+ # Add the backtests to the backtests list
342
+ loaded_backtests.extend(backtests)
343
+
344
+ if len(loaded_backtests) == 0:
345
+ raise OperationalException(
346
+ f"The directory {directory_path} is not a valid backtest report."
347
+ )
348
+
349
+ return BacktestReport(backtest=loaded_backtests)
@@ -0,0 +1,19 @@
1
+ from .equity_curve_drawdown import get_equity_curve_with_drawdown_chart
2
+ from .equity_curve import get_equity_curve_chart
3
+ from .rolling_sharp_ratio import get_rolling_sharpe_ratio_chart
4
+ from .monthly_returns_heatmap import get_monthly_returns_heatmap_chart
5
+ from .yearly_returns_barchart import get_yearly_returns_bar_chart
6
+ from .ohlcv_data_completeness import get_ohlcv_data_completeness_chart
7
+ from .entry_exist_signals import get_entry_and_exit_signals
8
+ from .line_chart import create_line_scatter
9
+
10
+ __all__ = [
11
+ "get_equity_curve_with_drawdown_chart",
12
+ "get_rolling_sharpe_ratio_chart",
13
+ "get_monthly_returns_heatmap_chart",
14
+ "get_yearly_returns_bar_chart",
15
+ "get_ohlcv_data_completeness_chart",
16
+ "get_entry_and_exit_signals",
17
+ "create_line_scatter",
18
+ "get_equity_curve_chart"
19
+ ]
@@ -0,0 +1,66 @@
1
+ import pandas as pd
2
+ import plotly.graph_objects as go
3
+
4
+
5
+ def get_entry_and_exit_signals(
6
+ entry_signals: pd.Series,
7
+ exit_signals: pd.Series,
8
+ price_data: pd.DataFrame
9
+ ):
10
+ """
11
+ Plots the price chart with entry and exit signals.
12
+
13
+ Args:
14
+ entry_signals (pd.Series): Series containing buy
15
+ signals with datetime index. Entry signals should
16
+ be boolean values.
17
+ exit_signals (pd.Series): Series containing exit signals
18
+ with datetime index. Exit signals should be boolean values.
19
+ price_data (pd.DataFrame): DataFrame containing price
20
+ data with datetime index and 'Close'
21
+
22
+ Returns:
23
+ go.Figure: Plotly figure with price chart and signals.
24
+ """
25
+ # Create the base candlestick or line chart
26
+ fig = go.Figure()
27
+
28
+ fig.add_trace(go.Scatter(
29
+ x=price_data.index,
30
+ y=price_data["Close"],
31
+ mode="lines",
32
+ name="Close Price",
33
+ line=dict(color="blue")
34
+ ))
35
+
36
+ # Entry points
37
+ entry_points = price_data[entry_signals]
38
+ fig.add_trace(go.Scatter(
39
+ x=entry_points.index,
40
+ y=entry_points["Close"],
41
+ mode="markers",
42
+ name="Entry",
43
+ marker=dict(symbol="triangle-up", color="green", size=10)
44
+ ))
45
+
46
+ # Exit points
47
+ exit_points = price_data[exit_signals]
48
+ fig.add_trace(go.Scatter(
49
+ x=exit_points.index,
50
+ y=exit_points["Close"],
51
+ mode="markers",
52
+ name="Exit",
53
+ marker=dict(symbol="triangle-down", color="red", size=10)
54
+ ))
55
+
56
+ # Layout settings
57
+ fig.update_layout(
58
+ title="Entry and Exit Signals",
59
+ xaxis_title="Date",
60
+ yaxis_title="Price",
61
+ legend=dict(x=0, y=1),
62
+ height=600,
63
+ template="plotly_white"
64
+ )
65
+
66
+ return fig
@@ -0,0 +1,37 @@
1
+ import pandas as pd
2
+ from plotly import graph_objects as go
3
+
4
+
5
+ def get_equity_curve_chart(equity_curve_series):
6
+ equity_curve_df = pd.DataFrame(
7
+ equity_curve_series, columns=["value", "datetime"]
8
+ )
9
+
10
+ # Normalize equity to start at 1
11
+ equity_curve_df["value"] = (
12
+ equity_curve_df["value"] / equity_curve_df["value"].iloc[0]
13
+ )
14
+
15
+ fig = go.Figure()
16
+
17
+ # Draw equity curve
18
+ fig.add_trace(
19
+ go.Scatter(
20
+ x=equity_curve_df["datetime"],
21
+ y=equity_curve_df["value"],
22
+ mode="lines",
23
+ line=dict(color="rgba(0, 128, 0, 0.8)", width=1),
24
+ name="Equity Curve",
25
+ hovertemplate="<b>Equity</b><br>%{x}<br>Value: %{y:.2f}<extra></extra>"
26
+ )
27
+ )
28
+
29
+ fig.update_layout(
30
+ xaxis=dict(title=None),
31
+ yaxis=dict(title="Cumulative Equity (log)", type="log"),
32
+ title="Equity Curve with Drawdown",
33
+ hovermode="x unified",
34
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
35
+ )
36
+
37
+ return fig
@@ -0,0 +1,74 @@
1
+ import pandas as pd
2
+ from plotly.subplots import make_subplots
3
+ from plotly import graph_objects as go
4
+
5
+
6
+ def get_equity_curve_with_drawdown_chart(equity_curve_series, drawdown_series):
7
+ equity_curve_df = pd.DataFrame(
8
+ equity_curve_series, columns=["value", "datetime"]
9
+ )
10
+ drawdown_df = pd.DataFrame(
11
+ drawdown_series, columns=["value", "datetime"]
12
+ )
13
+
14
+ # Normalize equity to start at 1
15
+ equity_curve_df["value"] = (
16
+ equity_curve_df["value"] / equity_curve_df["value"].iloc[0]
17
+ )
18
+
19
+ fig = make_subplots(
20
+ rows=2,
21
+ cols=1,
22
+ shared_xaxes=True,
23
+ vertical_spacing=0.05,
24
+ row_heights=[0.7, 0.3],
25
+ subplot_titles=["", ""]
26
+ )
27
+
28
+ # Draw equity curve
29
+ fig.add_trace(
30
+ go.Scatter(
31
+ x=equity_curve_df["datetime"],
32
+ y=equity_curve_df["value"],
33
+ mode="lines",
34
+ line=dict(color="rgba(0, 128, 0, 0.8)", width=1),
35
+ name="Equity Curve",
36
+ hovertemplate="<b>Equity</b><br>%{x}<br>Value: %{y:.2f}<extra></extra>"
37
+ ),
38
+ row=1,
39
+ col=1
40
+ )
41
+
42
+ # Drawdown area
43
+ fig.add_trace(
44
+ go.Scatter(
45
+ x=drawdown_df["datetime"],
46
+ y=drawdown_df["value"],
47
+ mode="lines",
48
+ fill="tozeroy",
49
+ fillcolor="rgba(255, 99, 71, 0.3)",
50
+ line=dict(color="rgba(255, 99, 71, 0.8)", width=1),
51
+ name="Drawdown"
52
+ ),
53
+ row=2,
54
+ col=1
55
+ )
56
+
57
+ # Final layout
58
+ fig.update_layout(
59
+ xaxis=dict(title=None),
60
+ yaxis=dict(title="Cumulative Equity (log)", type="log"),
61
+ xaxis2=dict(title=None),
62
+ yaxis2=dict(
63
+ title="Drawdown",
64
+ tickformat=".0%",
65
+ tickvals=[-0.2, -0.15, -0.1, -0.05, 0] # Clean % ticks
66
+ ),
67
+ template="plotly_white",
68
+ height=600,
69
+ showlegend=False,
70
+ hovermode="x unified", # Enables vertical hover line
71
+ margin=dict(l=0, r=0, t=0, b=0),
72
+ )
73
+
74
+ return fig
@@ -0,0 +1,11 @@
1
+ import plotly.graph_objects as go
2
+
3
+
4
+ def create_line_scatter(x, y, name, colour = 'blue'):
5
+ return go.Scatter(
6
+ x=x,
7
+ y=y,
8
+ mode='lines',
9
+ name=name,
10
+ line=dict(color=colour)
11
+ )
@@ -0,0 +1,70 @@
1
+ import pandas as pd
2
+ import plotly.graph_objects as go
3
+
4
+
5
+ def get_monthly_returns_heatmap_chart(monthly_return_series):
6
+ df = pd.DataFrame(monthly_return_series, columns=["Return", "Timestamp"])
7
+ df["Timestamp"] = pd.to_datetime(df["Timestamp"], errors='coerce')
8
+ df["Year"] = df["Timestamp"].dt.year
9
+ df["Month"] = df["Timestamp"].dt.strftime("%b")
10
+
11
+ month_order = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
12
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
13
+ df["Month"] = pd.Categorical(
14
+ df["Month"], categories=month_order, ordered=True
15
+ )
16
+
17
+ # Ensure all months are present for each year
18
+ all_years = df["Year"].unique()
19
+ all_months = pd.DataFrame(
20
+ [(year, month) for year in all_years for month in month_order],
21
+ columns=["Year", "Month"]
22
+ )
23
+ df = pd.merge(
24
+ all_months,
25
+ df,
26
+ on=["Year", "Month"],
27
+ how="left"
28
+ ).fillna({"Return": 0.0})
29
+
30
+ # Pivot to matrix form
31
+ pivot_df = df.pivot(index="Year", columns="Month", values="Return")
32
+ pivot_df = pivot_df.reindex(columns=month_order)
33
+ pivot_df = pivot_df.sort_index(ascending=True) # Change to ascending order
34
+
35
+ z = pivot_df.values
36
+ text = [
37
+ [f"{v * 100:.2f}%" if pd.notna(v) else "" for v in row] for row in z
38
+ ]
39
+ hover_template = "Year %{y}<br>Month %{x}<br>" + \
40
+ "Return: %{z:.2%}<extra></extra>"
41
+ fig = go.Figure(data=go.Heatmap(
42
+ z=z,
43
+ x=pivot_df.columns,
44
+ y=pivot_df.index,
45
+ text=text,
46
+ texttemplate="%{text}",
47
+ textfont={"size": 12},
48
+ colorscale="RdYlGn",
49
+ showscale=False,
50
+ hovertemplate=hover_template,
51
+ zmin=-0.1,
52
+ zmax=0.1
53
+ ))
54
+
55
+ fig.update_layout(
56
+ title="Monthly Returns Heatmap (%)",
57
+ xaxis_title="Month",
58
+ yaxis=dict(
59
+ title=None,
60
+ tickmode='array',
61
+ tickvals=list(pivot_df.index),
62
+ ticktext=[str(year) for year in pivot_df.index],
63
+ autorange=True # Remove 'reversed' to match ascending order
64
+ ),
65
+ template="plotly_white",
66
+ margin=dict(l=0, r=0, t=40, b=20),
67
+ height=350,
68
+ showlegend=False
69
+ )
70
+ return fig
@@ -0,0 +1,51 @@
1
+ import plotly.graph_objects as go
2
+ import pandas as pd
3
+ import plotly.io as pio
4
+
5
+ def get_ohlcv_data_completeness_chart(
6
+ df,
7
+ timeframe='1min',
8
+ windowsize=100,
9
+ title="OHLCV Data completenes"
10
+ ):
11
+ df = df.copy()
12
+ df['Datetime'] = pd.to_datetime(df['Datetime'])
13
+ df = df.sort_values('Datetime').tail(windowsize)
14
+ start = df['Datetime'].iloc[0]
15
+ end = df['Datetime'].iloc[-1]
16
+ freq = pd.to_timedelta(timeframe)
17
+ expected = pd.date_range(start, end, freq=freq)
18
+ actual = df['Datetime']
19
+ missing = expected.difference(actual)
20
+
21
+ # Calculte the percentage completeness
22
+ completeness = len(actual) / len(expected) * 100
23
+ title += f" ({completeness:.2f}% complete)"
24
+ fig = go.Figure()
25
+ fig.add_trace(
26
+ go.Scatter(
27
+ x=actual,
28
+ y=[1]*len(actual),
29
+ mode='markers',
30
+ name='Present',
31
+ marker=dict(color='green', size=6)
32
+ )
33
+ )
34
+ fig.add_trace(
35
+ go.Scatter(
36
+ x=missing,
37
+ y=[1]*len(missing),
38
+ mode='markers',
39
+ name='Missing',
40
+ marker=dict(color='red', size=6, symbol='x')
41
+ )
42
+ )
43
+ fig.update_layout(
44
+ title=title,
45
+ xaxis_title='Datetime',
46
+ yaxis=dict(showticklabels=False),
47
+ height=300,
48
+ showlegend=True
49
+ )
50
+
51
+ return pio.to_html(fig, full_html=False, include_plotlyjs='cdn')