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,79 @@
1
+ import pandas as pd
2
+ import plotly.graph_objects as go
3
+
4
+
5
+ def get_rolling_sharpe_ratio_chart(rolling_sharpe_ratio_series):
6
+ """
7
+ Generates a Plotly figure showing the rolling Sharpe ratio series.
8
+
9
+ Args:
10
+ rolling_sharpe_ratio_series: List of tuples with rolling Sharpe
11
+ ratio data. Each tuple should contain a Sharpe ratio
12
+ value and the corresponding timestamp.
13
+ Returns:
14
+ plotly.graph_objects.Figure: A Plotly figure containing
15
+ the rolling Sharpe ratio chart.
16
+ """
17
+ results = rolling_sharpe_ratio_series
18
+ rolling_sharpe_ratio_df = pd.DataFrame(
19
+ results, columns=['sharpe_ratio', 'timestamp']
20
+ )
21
+ rolling_sharpe_ratio_df['timestamp'] = pd.to_datetime(
22
+ rolling_sharpe_ratio_df['timestamp']
23
+ )
24
+
25
+ fig = go.Figure()
26
+ fig.add_trace(
27
+ go.Scatter(
28
+ x=rolling_sharpe_ratio_df['timestamp'],
29
+ y=rolling_sharpe_ratio_df['sharpe_ratio'],
30
+ mode='lines',
31
+ name='Rolling Sharpe Ratio',
32
+ line=dict(color='#1f77b4', width=2)
33
+ )
34
+ )
35
+
36
+ last_nan_row = rolling_sharpe_ratio_df[
37
+ rolling_sharpe_ratio_df['sharpe_ratio'].isna()
38
+ ]
39
+ if not last_nan_row.empty:
40
+ last_nan_date = last_nan_row['timestamp'].max()
41
+ fig.add_vline(
42
+ x=last_nan_date,
43
+ line=dict(color='grey', width=1, dash='dash')
44
+ )
45
+
46
+ fig.update_layout(
47
+ xaxis=dict(
48
+ showgrid=True,
49
+ gridcolor='lightgray',
50
+ tickformat='%b %Y',
51
+ tickfont=dict(color='black'),
52
+ showline=True,
53
+ linecolor='black',
54
+ ticks='outside',
55
+ tickcolor='black'
56
+ ),
57
+ yaxis=dict(
58
+ title='Rolling Sharpe Ratio',
59
+ showgrid=True,
60
+ gridcolor='lightgray',
61
+ tickfont=dict(color='black'),
62
+ showline=True,
63
+ linecolor='black',
64
+ ticks='outside',
65
+ tickcolor='black'
66
+ ),
67
+ plot_bgcolor='white',
68
+ paper_bgcolor='white',
69
+ font=dict(color='black'),
70
+ hovermode='x unified',
71
+ legend=dict(
72
+ font=dict(color='black'),
73
+ bgcolor='rgba(0,0,0,0)',
74
+ bordercolor='black'
75
+ ),
76
+ height=300,
77
+ margin=dict(l=20, r=20, t=30, b=20)
78
+ )
79
+ return fig
@@ -0,0 +1,55 @@
1
+ import pandas as pd
2
+ import plotly.express as px
3
+
4
+
5
+ def get_yearly_returns_bar_chart(yearly_returns_series):
6
+ """
7
+ Create a bar chart showing yearly returns.
8
+ This chart visualizes the yearly returns of the backtest report.
9
+
10
+ Args:
11
+ yearly_returns_series: The yearly returns data as a series.
12
+
13
+ Returns:
14
+ A Plotly Figure object representing the yearly returns bar chart.
15
+ """
16
+ # Convert the series to a DataFrame
17
+ df = pd.DataFrame(yearly_returns_series, columns=["Return", "Year"])
18
+
19
+ # Ensure the 'Year' column is datetime-like
20
+ df["Year"] = pd.to_datetime(df["Year"], errors="coerce")
21
+
22
+ # Extract the year from the datetime
23
+ df["Year"] = df["Year"].dt.year
24
+
25
+ # Convert returns to percentage
26
+ df["Return"] = df["Return"] * 100 # Convert to percentage
27
+
28
+ # Ensure that there are no floating point numbers in the Return column
29
+ df["Return"] = df["Return"].round(0).astype(int)
30
+
31
+ # Create the bar chart
32
+ fig = px.bar(
33
+ df,
34
+ x="Year",
35
+ y="Return",
36
+ text="Return",
37
+ title="Yearly Returns (%)",
38
+ color="Return",
39
+ color_continuous_scale=["red", "green"]
40
+ )
41
+
42
+ # Update layout for padding and formatting
43
+ fig.update_layout(
44
+ xaxis=dict(title="Time", tickmode="linear"),
45
+ yaxis=dict(title=None, tickformat="", range=[-10, 30]),
46
+ showlegend=False,
47
+ coloraxis_showscale=False, # Disable the color scale legend
48
+ height=350,
49
+ margin=dict(l=0, r=0, t=40, b=20),
50
+ )
51
+
52
+ # Format bar text
53
+ fig.update_traces(texttemplate="%{text}", textposition="outside")
54
+ return fig
55
+
@@ -0,0 +1,185 @@
1
+ import os
2
+ import logging
3
+ import pandas as pd
4
+ from jinja2 import Environment, FileSystemLoader
5
+
6
+ from .tables import create_html_time_metrics_table, \
7
+ create_html_trade_metrics_table, create_html_key_metrics_table, \
8
+ create_html_trades_table
9
+ from .charts import get_equity_curve_with_drawdown_chart, \
10
+ get_rolling_sharpe_ratio_chart, get_monthly_returns_heatmap_chart, \
11
+ get_yearly_returns_bar_chart, get_ohlcv_data_completeness_chart
12
+ from investing_algorithm_framework.domain import TimeFrame
13
+
14
+
15
+ logger = logging.getLogger("investing_algorithm_framework")
16
+
17
+
18
+ def get_symbol_from_file_name(file_name: str) -> str:
19
+ """
20
+ Extract the symbol from the file name.
21
+
22
+ Args:
23
+ file_name (str): The file name from which to extract the symbol.
24
+
25
+ Returns:
26
+ str: The extracted symbol.
27
+ """
28
+ # Assuming the file name format is "symbol_timeframe.csv"
29
+ return file_name.split('_')[0].upper()
30
+
31
+ def get_market_from_file_name(file_name: str) -> str:
32
+ """
33
+ Extract the market from the file name.
34
+
35
+ Args:
36
+ file_name (str): The file name from which to extract the market.
37
+
38
+ Returns:
39
+ str: The extracted market.
40
+ """
41
+ # Assuming the file name format is "symbol_market_timeframe.csv"
42
+ parts = file_name.split('_')
43
+ if len(parts) < 2:
44
+ raise ValueError("File name does not contain a valid market.")
45
+ return parts[1].upper()
46
+
47
+
48
+ def get_time_frame_from_file_name(file_name: str) -> TimeFrame:
49
+ """
50
+ Extract the time frame from the file name.
51
+
52
+ Args:
53
+ file_name (str): The file name from which to extract the time frame.
54
+
55
+ Returns:
56
+ TimeFrame: The extracted time frame.
57
+ """
58
+ parts = file_name.split('_')
59
+
60
+ if len(parts) < 3:
61
+ raise ValueError(
62
+ "File name does not contain a valid time frame."
63
+ )
64
+ time_frame_str = parts[3]
65
+
66
+ try:
67
+ return TimeFrame.from_string(time_frame_str)
68
+ except ValueError:
69
+ raise ValueError(
70
+ f"Could not extract time frame from file name: {file_path}. "
71
+ f"Expected format 'OHLCV_<SYMBOL>_<MARKET>_<TIME_FRAME>_<START_DATE>_<END_DATE>.csv', "
72
+ f"got '{time_frame_str}'."
73
+ )
74
+
75
+
76
+ def add_html_report(report) -> "BacktestReport":
77
+ """
78
+ Add HTML content to the report.results.
79
+
80
+ Args:
81
+ report.results (Backtestreport.results): The backtest report.results to which the HTML
82
+ content will be added.
83
+
84
+ Returns:
85
+ Backtestreport: The updated report with HTML content.
86
+ """
87
+ metrics = report.metrics
88
+ # Create plots
89
+ equity_with_drawdown_fig = get_equity_curve_with_drawdown_chart(
90
+ metrics["Equity Curve"], metrics["Drawdown series"]
91
+ )
92
+ equity_with_drawdown_plot_html = equity_with_drawdown_fig.to_html(
93
+ full_html=False, include_plotlyjs='cdn',
94
+ config={'responsive': True}, default_width="90%"
95
+ )
96
+ rolling_sharpe_ratio_fig = get_rolling_sharpe_ratio_chart(
97
+ metrics["Rolling Sharpe Ratio"]
98
+ )
99
+ rolling_sharpe_ratio_plot_html = rolling_sharpe_ratio_fig.to_html(
100
+ full_html=False, include_plotlyjs='cdn',
101
+ config={'responsive': True}, default_width="90%"
102
+ )
103
+ monthly_returns_heatmap_fig = get_monthly_returns_heatmap_chart(
104
+ metrics["Monthly Returns"]
105
+ )
106
+ monthly_returns_heatmap_html = monthly_returns_heatmap_fig.to_html(
107
+ full_html=False, include_plotlyjs='cdn',
108
+ config={'responsive': True}
109
+ )
110
+ yearly_returns_histogram_fig = get_yearly_returns_bar_chart(
111
+ metrics["Yearly Returns"]
112
+ )
113
+ yearly_returns_histogram_html = yearly_returns_histogram_fig.to_html(
114
+ full_html=False, include_plotlyjs='cdn',
115
+ config={'responsive': True}
116
+ )
117
+
118
+ # Create OHLCV data completeness charts
119
+ data_files = report.data_files
120
+ ohlcv_data_completeness_charts_html = ""
121
+
122
+ for file in data_files:
123
+ try:
124
+ if file.endswith('.csv'):
125
+ df = pd.read_csv(file, parse_dates=['Datetime'])
126
+ file_name = os.path.basename(file)
127
+ symbol = get_symbol_from_file_name(file_name)
128
+ market = get_market_from_file_name(file_name)
129
+ time_frame = get_time_frame_from_file_name(file_name)
130
+ title = f"OHLCV Data Completeness for {market} - {symbol} - {time_frame.value}"
131
+ ohlcv_data_completeness_chart_html = \
132
+ get_ohlcv_data_completeness_chart(
133
+ df,
134
+ timeframe=time_frame.value,
135
+ windowsize=200,
136
+ title=title
137
+ )
138
+
139
+ ohlcv_data_completeness_charts_html += (
140
+ '<div class="ohlcv-data-completeness-chart">'
141
+ f'{ohlcv_data_completeness_chart_html}'
142
+ '</div>'
143
+ )
144
+
145
+ except Exception as e:
146
+ logger.warning(
147
+ "Error creating OHLCV data completeness " +
148
+ f"chart for {file}: {e}"
149
+ )
150
+ continue
151
+
152
+ # Create HTML tables
153
+ key_metrics_table_html = create_html_key_metrics_table(
154
+ metrics, report.results
155
+ )
156
+ trades_metrics_table_html = create_html_trade_metrics_table(
157
+ metrics, report.results
158
+ )
159
+ time_metrics_table_html = create_html_time_metrics_table(
160
+ metrics, report.results
161
+ )
162
+ trades_table_html = create_html_trades_table(
163
+ report.results
164
+ )
165
+
166
+ # Jinja2 environment setup
167
+ template_dir = os.path.join(os.path.dirname(__file__), 'templates')
168
+ env = Environment(loader=FileSystemLoader(template_dir))
169
+ template = env.get_template('report_template.html.j2')
170
+
171
+ # Render template with variables
172
+ html_rendered = template.render(
173
+ report=report,
174
+ equity_with_drawdown_plot_html=equity_with_drawdown_plot_html,
175
+ rolling_sharpe_ratio_plot_html=rolling_sharpe_ratio_plot_html,
176
+ monthly_returns_heatmap_html=monthly_returns_heatmap_html,
177
+ yearly_returns_histogram_html=yearly_returns_histogram_html,
178
+ key_metrics_table_html=key_metrics_table_html,
179
+ trades_metrics_table_html=trades_metrics_table_html,
180
+ time_metrics_table_html=time_metrics_table_html,
181
+ trades_table_html=trades_table_html,
182
+ data_completeness_charts_html=ohlcv_data_completeness_charts_html
183
+ )
184
+ report.html_report = html_rendered
185
+ return report
@@ -0,0 +1,11 @@
1
+ from .trades_table import create_html_trades_table
2
+ from .key_metrics_table import create_html_key_metrics_table
3
+ from .trade_metrics_table import create_html_trade_metrics_table
4
+ from .time_metrics_table import create_html_time_metrics_table
5
+
6
+ __all__ = [
7
+ "create_html_trades_table",
8
+ "create_html_key_metrics_table",
9
+ "create_html_trade_metrics_table",
10
+ "create_html_time_metrics_table"
11
+ ]
@@ -0,0 +1,217 @@
1
+ import pandas as pd
2
+
3
+ from .utils import safe_format, safe_format_percentage
4
+
5
+
6
+ def highlight_sharpe_and_sortino(row):
7
+ """
8
+ | -------------- | ------------------------------------------- |
9
+ | **< 0** | Bad: Underperforms risk-free asset |
10
+ | **0.0 – 1.0** | Suboptimal: Returns do not justify risk |
11
+ | **1.0 – 1.99** | Acceptable: Reasonable risk-adjusted return |
12
+ | **2.0 – 2.99** | Good: Strong risk-adjusted performance |
13
+ | **3.0+** | Excellent: Exceptional risk-adjusted return |
14
+ """
15
+ metric = row['Metric']
16
+ try:
17
+ value = float(row['Value'])
18
+ except Exception:
19
+ value = None
20
+ styles = pd.Series('', index=row.index)
21
+ if metric in ['Sharpe Ratio', 'Sortino Ratio'] and value is not None:
22
+
23
+ if value < 0:
24
+ styles['Value'] = 'color: #B22222; font-weight: bold;' # red
25
+ elif 0 <= value < 1:
26
+ styles['Value'] = 'color: #FFA500; font-weight: bold;' # orange
27
+ elif 1 <= value < 2:
28
+ styles['Value'] = 'color: #FFD700; font-weight: bold;' # gold
29
+ elif 2 <= value < 3:
30
+ styles['Value'] = 'color: #32CD32; font-weight: bold;' # lime green
31
+ elif value >= 3:
32
+ styles['Value'] = 'color: #228B22; font-weight: bold;' # dark green
33
+ return styles
34
+
35
+
36
+ def highlight_profit_factor(row):
37
+ """
38
+ | **< 1.0** | **Losing strategy** — losses outweigh profits |
39
+ | **1.0 – 1.3** | Weak or barely breakeven — needs improvement or may not be sustainable |
40
+ | **1.3 – 1.6** | Average — possibly profitable but sensitive to market regime changes |
41
+ | **1.6 – 2.0** | Good — generally indicates a solid, sustainable edge |
42
+ | **2.0 – 3.0** | Very good — strong edge with lower drawdown risk |
43
+ | **> 3.0** | Excellent — rare in real markets; often associated with low-frequency or niche strategies |
44
+ """
45
+ metric = row['Metric']
46
+ try:
47
+ value = float(row['Value'])
48
+ except Exception:
49
+ value = None
50
+ styles = pd.Series('', index=row.index)
51
+ if metric == 'Profit Factor' and value is not None:
52
+ if value < 1.0:
53
+ styles['Value'] = 'color: #B22222; font-weight: bold;' # red
54
+ elif 1.0 <= value < 1.3:
55
+ styles['Value'] = 'color: #FFA500; font-weight: bold;' # orange
56
+ elif 1.3 <= value < 1.6:
57
+ styles['Value'] = 'color: #FFD700; font-weight: bold;' # gold
58
+ elif 1.6 <= value < 2.0:
59
+ styles['Value'] = 'color: #32CD32; font-weight: bold;' # lime green
60
+ elif 2.0 <= value < 3.0:
61
+ styles['Value'] = 'color: #228B22; font-weight: bold;' # dark green
62
+ elif value >= 3.0:
63
+ styles['Value'] = 'color: #006400; font-weight: bold;' # dark green
64
+ return styles
65
+
66
+ def highlight_calmar_ratio(row):
67
+ """
68
+ | **> 3.0** | **Excellent** – strong return vs. drawdown |
69
+ | **2.0 – 3.0** | **Very Good** – solid risk-adjusted performance |
70
+ | **1.0 – 2.0** | **Acceptable** – decent, especially for volatile strategies |
71
+ | **< 1.0** | **Poor** – high drawdowns relative to return |
72
+ """
73
+ metric = row['Metric']
74
+ try:
75
+ value = float(row['Value'])
76
+ except Exception:
77
+ value = None
78
+ styles = pd.Series('', index=row.index)
79
+ if metric == 'Calmar Ratio' and value is not None:
80
+ if value > 3.0:
81
+ styles['Value'] = 'color: #006400; font-weight: bold;' # dark green
82
+ elif 2.0 <= value <= 3.0:
83
+ styles['Value'] = 'color: #228B22; font-weight: bold;' # green
84
+ elif 1.0 <= value < 2.0:
85
+ styles['Value'] = 'color: #32CD32; font-weight: bold;' # lime green
86
+ elif value < 1.0:
87
+ styles['Value'] = 'color: #B22222; font-weight: bold;' # red
88
+ return styles
89
+
90
+
91
+ def highlight_annual_volatility(row):
92
+ """
93
+ | **Annual Volatility** | **Risk Level** | **Typical for...** | **Comments** |
94
+ | --------------------- | -------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
95
+ | **< 5%** | Very Low Risk | Cash equivalents, short-term bonds | Low return expectations; often used for capital preservation |
96
+ | **5% – 10%** | Low Risk | Diversified bond portfolios, conservative allocation strategies | Suitable for risk-averse investors |
97
+ | **10% – 15%** | Moderate Risk | Balanced portfolios, large-cap equity indexes (e.g., S\&P 500 ≈ \~15%) | Standard for traditional diversified portfolios |
98
+ | **15% – 25%** | High Risk | Growth stocks, hedge funds, active equity strategies | Higher return potential, but more drawdowns |
99
+ | **> 25%** | Very High Risk | Crypto, leveraged ETFs, speculative strategies | High potential returns, but prone to large losses; often not suitable long-term |
100
+ """
101
+ metric = row['Metric']
102
+ try:
103
+ value = float(row['Value'].strip('%')) / 100 # Convert percentage string to float
104
+ except Exception:
105
+ value = None
106
+ styles = pd.Series('', index=row.index)
107
+ if metric == 'Annual Volatility' and value is not None:
108
+ if value < 0.05:
109
+ styles['Value'] = 'color: #006400; font-weight: bold;' # dark green
110
+ elif 0.05 <= value < 0.10:
111
+ styles['Value'] = 'color: #32CD32; font-weight: bold;' # lime green
112
+ elif 0.10 <= value < 0.15:
113
+ styles['Value'] = 'color: #FFD700; font-weight: bold;' # gold
114
+ elif 0.15 <= value < 0.25:
115
+ styles['Value'] = 'color: #FFA500; font-weight: bold;' # orange
116
+ elif value >= 0.25:
117
+ styles['Value'] = 'color: #B22222; font-weight: bold;' # red
118
+ return styles
119
+
120
+
121
+ def highlight_max_drawdown(row):
122
+ """
123
+ | **Max Drawdown (%)** | **Interpretation** |
124
+ |-----------------------|----------------------------------------------------------------------|
125
+ | **0% to -5%** | 🟢 Excellent — Very low risk, typical for conservative strategies |
126
+ | **-5% to -10%** | ✅ Good — Moderate volatility, acceptable for balanced portfolios |
127
+ | **-10% to -20%** | ⚠️ Elevated Risk — Common in growth or actively managed strategies |
128
+ | **-20% to -40%** | 🔻 High Risk — Significant drawdown, typical of aggressive strategies |
129
+ | **> -40%** | 🚨 Very High Risk — Risk of capital loss or strategy failure |
130
+ """
131
+ metric = row['Metric']
132
+ try:
133
+ value = float(row['Value'].strip('%'))
134
+ except Exception:
135
+ value = None
136
+ styles = pd.Series('', index=row.index)
137
+ if metric == 'Max Drawdown' and value is not None:
138
+ if value < 5:
139
+ styles['Value'] = 'color: #006400; font-weight: bold;' # dark green
140
+ elif 10 >= value > 5:
141
+ styles['Value'] = 'color: #32CD32; font-weight: bold;' # lime green
142
+ elif 20 >= value > 10:
143
+ styles['Value'] = 'color: #FFD700; font-weight: bold;' # gold
144
+ elif 40 >= value > 20:
145
+ styles['Value'] = 'color: #FFA500; font-weight: bold;' # orange
146
+ elif value >= 40:
147
+ styles['Value'] = 'color: #B22222; font-weight: bold;' # red
148
+ return styles
149
+
150
+
151
+ def create_html_key_metrics_table(results, report):
152
+ copy_results = results.to_dict().copy()
153
+ format_str = "{:.2f}"
154
+
155
+ # Format some values to percentages and floats
156
+ copy_results['Total Return'] = (
157
+ f"{safe_format(copy_results['total_net_gain'], format_str)} "
158
+ f"({safe_format_percentage(copy_results['total_net_gain_percentage'], format_str)}%)"
159
+ )
160
+ copy_results['CAGR'] = f"{safe_format_percentage(copy_results['cagr'],format_str)}%"
161
+ copy_results['Sharpe Ratio'] = safe_format(copy_results['sharpe_ratio'], format_str)
162
+ copy_results['Sortino Ratio'] = safe_format(copy_results['sortino_ratio'], format_str)
163
+ copy_results['Profit Factor'] = safe_format(copy_results['profit_factor'], format_str)
164
+ copy_results['Calmar Ratio'] = safe_format(copy_results['calmar_ratio'], format_str)
165
+ copy_results['Annual Volatility'] = f"{safe_format_percentage(copy_results['annual_volatility'], format_str)}%"
166
+ copy_results['Max Drawdown'] = f"{safe_format_percentage(copy_results['max_drawdown'], format_str)}%"
167
+ copy_results['Max Drawdown Absolute'] = f"{safe_format(copy_results['max_drawdown_absolute'], format_str)} {report.trading_symbol}"
168
+ copy_results['Max Daily Drawdown'] = f"{safe_format_percentage(copy_results['max_daily_drawdown'], format_str)}%"
169
+ copy_results['Max Drawdown Duration'] = f"{copy_results['max_drawdown_duration']} hours - {copy_results['max_drawdown_duration'] // 24} days"
170
+
171
+ stats = {
172
+ "Metric": [
173
+ "Total Return",
174
+ "CAGR",
175
+ "Sharpe Ratio",
176
+ "Sortino Ratio",
177
+ "Profit Factor",
178
+ "Calmar Ratio",
179
+ "Annual Volatility",
180
+ "Max Drawdown",
181
+ "Max Drawdown Absolute",
182
+ "Max Daily Drawdown",
183
+ "Max Drawdown Duration"
184
+ ],
185
+ "Value": [
186
+ copy_results['Total Return'],
187
+ copy_results['CAGR'],
188
+ copy_results['Sharpe Ratio'],
189
+ copy_results['Sortino Ratio'],
190
+ copy_results['Profit Factor'],
191
+ copy_results['Calmar Ratio'],
192
+ copy_results['Annual Volatility'],
193
+ copy_results['Max Drawdown'],
194
+ copy_results['Max Drawdown Absolute'],
195
+ copy_results['Max Daily Drawdown'],
196
+ copy_results['Max Drawdown Duration']
197
+ ]
198
+ }
199
+
200
+ df_stats = pd.DataFrame(stats)
201
+
202
+ table_html = (
203
+ df_stats.style
204
+ .apply(highlight_sharpe_and_sortino, axis=1)
205
+ .apply(highlight_profit_factor, axis=1)
206
+ .apply(highlight_calmar_ratio, axis=1)
207
+ .apply(highlight_annual_volatility, axis=1)
208
+ .apply(highlight_max_drawdown, axis=1)
209
+ .set_table_styles([
210
+ {'selector': 'th', 'props': [('background-color', '#f2f2f2'), ('font-weight', 'bold')]},
211
+ {'selector': 'td', 'props': [('font-size', '14px')]},
212
+ {'selector': 'tr:nth-child(even)', 'props': [('background-color', '#fafafa')]}
213
+ ])
214
+ .hide(axis='index')
215
+ .to_html()
216
+ )
217
+ return table_html
@@ -0,0 +1,80 @@
1
+ import pandas as pd
2
+
3
+ from .utils import safe_format_percentage, safe_format_date
4
+
5
+
6
+ def create_html_time_metrics_table(results, report):
7
+ copy_results = results.to_dict().copy()
8
+ start_date = report.backtest_start_date
9
+ end_date = report.backtest_end_date
10
+ string_format = "{:.2f}"
11
+ # Format dates
12
+ copy_results['Start Date'] = safe_format_date(start_date, "%Y-%m-%d %H:%M")
13
+ copy_results['End Date'] = safe_format_date(end_date, "%Y-%m-%d %H:%M")
14
+ copy_results['Percentage Winning Months'] = f"{safe_format_percentage(copy_results['percentage_winning_months'], string_format)}%"
15
+ copy_results['Percentage Winning Years'] = f"{safe_format_percentage(copy_results['percentage_winning_years'], string_format)}%"
16
+ copy_results['Average Monthly Return'] = f"{safe_format_percentage(copy_results['average_monthly_return'], string_format)}%"
17
+ copy_results['Average Monthly Return (Losing Months)'] = f"{safe_format_percentage(copy_results['average_monthly_return_losing_months'], string_format)}%"
18
+ copy_results['Average Monthly Return (Winning Months)'] = f"{safe_format_percentage(copy_results['average_monthly_return_winning_months'], string_format)}%"
19
+
20
+ if isinstance(copy_results['best_month'], tuple):
21
+ percentage = copy_results['best_month'][0]
22
+ date = copy_results['best_month'][1]
23
+ copy_results['Best Month'] = f"{safe_format_percentage(percentage, string_format)}% {safe_format_date(date, '%b %Y')}"
24
+
25
+ if isinstance(copy_results['worst_month'], tuple):
26
+ percentage = copy_results['worst_month'][0]
27
+ date = copy_results['worst_month'][1]
28
+ copy_results['Worst Month'] = f"{safe_format_percentage(percentage, string_format)}% {safe_format_date(date, '%b %Y')}"
29
+
30
+ if isinstance(copy_results['best_year'], tuple):
31
+ percentage = copy_results['best_year'][0]
32
+ date = copy_results['best_year'][1]
33
+ copy_results['Best Year'] = f"{safe_format_percentage(percentage, string_format)}% {safe_format_date(date, '%b %Y')}"
34
+ if isinstance(copy_results['worst_year'], tuple):
35
+ percentage = copy_results['worst_year'][0]
36
+ date = copy_results['worst_year'][1]
37
+ copy_results['Worst Year'] = f"{safe_format_percentage(percentage, string_format)}% {safe_format_date(date, '%b %Y')}"
38
+
39
+ stats = {
40
+ "Metric": [
41
+ "Start Date",
42
+ "End Date",
43
+ "% Winning Months",
44
+ "% Winning Years",
45
+ "AVG Mo Return",
46
+ "AVG Mo Return (Losing Months)",
47
+ "AVG Mo Return (Winning Months)",
48
+ "Best Month",
49
+ "Worst Month",
50
+ "Best Year",
51
+ "Worst Year",
52
+ ],
53
+ "Value": [
54
+ copy_results['Start Date'],
55
+ copy_results['End Date'],
56
+ copy_results['Percentage Winning Months'],
57
+ copy_results['Percentage Winning Years'],
58
+ copy_results['Average Monthly Return'],
59
+ copy_results['Average Monthly Return (Losing Months)'],
60
+ copy_results['Average Monthly Return (Winning Months)'],
61
+ copy_results['Best Month'],
62
+ copy_results['Worst Month'],
63
+ copy_results['Best Year'],
64
+ copy_results['Worst Year'],
65
+ ]
66
+ }
67
+
68
+ df_stats = pd.DataFrame(stats)
69
+
70
+ table_html = (
71
+ df_stats.style
72
+ .set_table_styles([
73
+ {'selector': 'th', 'props': [('background-color', '#f2f2f2'), ('font-weight', 'bold')]},
74
+ {'selector': 'td', 'props': [('font-size', '14px')]},
75
+ {'selector': 'tr:nth-child(even)', 'props': [('background-color', '#fafafa')]}
76
+ ])
77
+ .hide(axis='index')
78
+ .to_html()
79
+ )
80
+ return table_html