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,358 @@
1
+ from typing import List
2
+ from logging import getLogger
3
+
4
+ from investing_algorithm_framework.domain import BacktestMetrics, \
5
+ BacktestRun, OperationalException, Backtest, BacktestDateRange
6
+ from .cagr import get_cagr
7
+ from .calmar_ratio import get_calmar_ratio
8
+ from .drawdown import get_drawdown_series, get_max_drawdown, \
9
+ get_max_daily_drawdown, get_max_drawdown_absolute, \
10
+ get_max_drawdown_duration
11
+ from .equity_curve import get_equity_curve
12
+ from .exposure import get_exposure_ratio, get_cumulative_exposure, \
13
+ get_trades_per_year, get_trades_per_day
14
+ from .profit_factor import get_profit_factor, get_gross_loss, get_gross_profit
15
+ from .returns import get_monthly_returns, get_yearly_returns, \
16
+ get_worst_year, get_best_year, get_best_month, get_worst_month, \
17
+ get_percentage_winning_months, get_percentage_winning_years, \
18
+ get_average_monthly_return, get_average_monthly_return_winning_months, \
19
+ get_average_monthly_return_losing_months, get_cumulative_return, \
20
+ get_cumulative_return_series
21
+ from .returns import get_total_return, get_final_value, get_total_loss, \
22
+ get_total_growth
23
+ from .sharpe_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
24
+ from .sortino_ratio import get_sortino_ratio
25
+ from .volatility import get_annual_volatility
26
+ from .win_rate import get_win_rate, get_win_loss_ratio, get_current_win_rate, \
27
+ get_current_win_loss_ratio
28
+ from .trades import get_average_trade_duration, get_average_trade_size, \
29
+ get_number_of_trades, get_positive_trades, get_number_of_closed_trades, \
30
+ get_negative_trades, get_average_trade_return, get_number_of_open_trades, \
31
+ get_worst_trade, get_best_trade, get_average_trade_gain, \
32
+ get_average_trade_loss, get_median_trade_return, \
33
+ get_current_average_trade_gain, get_current_average_trade_return, \
34
+ get_current_average_trade_duration, get_current_average_trade_loss
35
+
36
+ logger = getLogger("investing_algorithm_framework")
37
+
38
+ def create_backtest_metrics_for_backtest(
39
+ backtest: Backtest,
40
+ risk_free_rate: float, metrics: List[str] = None,
41
+ backtest_date_range: BacktestDateRange = None
42
+ ) -> Backtest:
43
+
44
+ """
45
+ Create BacktestMetrics for a Backtest object.
46
+
47
+ Args:
48
+ backtest (Backtest): The Backtest object containing
49
+ backtest runs.
50
+ risk_free_rate (float): The risk-free rate used in certain
51
+ metric calculations.
52
+ metrics (List[str], optional): List of metric names to compute.
53
+ If None, a default set of metrics will be computed.
54
+ backtest_date_range (BacktestDateRange, optional): The date range
55
+ for the backtest. If None, all backtest metrics will be computed
56
+ for each backtest run.
57
+
58
+ Returns:
59
+ Backtest: The Backtest object with computed metrics for each run.
60
+ """
61
+ if backtest_date_range is not None:
62
+ backtest_runs = [
63
+ backtest.get_backtest_run(backtest_date_range)
64
+ ]
65
+ else:
66
+ backtest_runs = backtest.get_all_backtest_runs()
67
+
68
+ for backtest_run in backtest_runs:
69
+ # If a date range is provided, check if the backtest run falls
70
+ # within the range
71
+ backtest_metrics = create_backtest_metrics(
72
+ backtest_run, risk_free_rate, metrics
73
+ )
74
+ backtest_run.backtest_metrics = backtest_metrics
75
+
76
+ backtest.backtest_runs = backtest_runs
77
+ return backtest
78
+
79
+
80
+ def create_backtest_metrics(
81
+ backtest_run: BacktestRun, risk_free_rate: float, metrics: List[str] = None
82
+ ) -> BacktestMetrics:
83
+ """
84
+ Create a BacktestMetrics instance and optionally save it to a file.
85
+
86
+ Args:
87
+ backtest_run (BacktestRun): The BacktestRun object containing
88
+ portfolio snapshots and trades.
89
+ risk_free_rate (float): The risk-free rate used in certain
90
+ metric calculations.
91
+ metrics (List[str], optional): List of metric names to compute.
92
+ If None, a default set of metrics will be computed.
93
+
94
+ Returns:
95
+ BacktestMetrics: The computed backtest metrics.
96
+ """
97
+
98
+ if metrics is None:
99
+ metrics = [
100
+ "backtest_start_date",
101
+ "backtest_end_date",
102
+ "equity_curve",
103
+ "final_value",
104
+ "total_growth",
105
+ "total_growth_percentage",
106
+ "total_net_gain",
107
+ "total_net_gain_percentage",
108
+ "total_loss",
109
+ "total_loss_percentage",
110
+ "cumulative_return",
111
+ "cumulative_return_series",
112
+ "cagr",
113
+ "sharpe_ratio",
114
+ "rolling_sharpe_ratio",
115
+ "sortino_ratio",
116
+ "calmar_ratio",
117
+ "profit_factor",
118
+ "annual_volatility",
119
+ "monthly_returns",
120
+ "yearly_returns",
121
+ "drawdown_series",
122
+ "max_drawdown",
123
+ "max_drawdown_absolute",
124
+ "max_daily_drawdown",
125
+ "max_drawdown_duration",
126
+ "trades_per_year",
127
+ "trade_per_day",
128
+ "exposure_ratio",
129
+ "cumulative_exposure",
130
+ "best_trade",
131
+ "worst_trade",
132
+ "number_of_positive_trades",
133
+ "percentage_positive_trades",
134
+ "number_of_negative_trades",
135
+ "percentage_negative_trades",
136
+ "average_trade_duration",
137
+ "average_trade_size",
138
+ "average_trade_loss",
139
+ "average_trade_loss_percentage",
140
+ "average_trade_gain",
141
+ "average_trade_gain_percentage",
142
+ "average_trade_return",
143
+ "average_trade_return_percentage",
144
+ "median_trade_return",
145
+ "number_of_trades",
146
+ "number_of_trades_closed",
147
+ "number_of_trades_opened",
148
+ "number_of_trades_open_at_end",
149
+ "win_rate",
150
+ "current_win_rate",
151
+ "win_loss_ratio",
152
+ "current_win_loss_ratio",
153
+ "percentage_winning_months",
154
+ "percentage_winning_years",
155
+ "average_monthly_return",
156
+ "average_monthly_return_losing_months",
157
+ "average_monthly_return_winning_months",
158
+ "best_month",
159
+ "best_year",
160
+ "worst_month",
161
+ "worst_year",
162
+ "total_number_of_days",
163
+ "current_average_trade_gain",
164
+ "current_average_trade_return",
165
+ "current_average_trade_duration",
166
+ "current_average_trade_loss",
167
+ ]
168
+
169
+ backtest_metrics = BacktestMetrics(
170
+ backtest_start_date=backtest_run.backtest_start_date,
171
+ backtest_end_date=backtest_run.backtest_end_date,
172
+ )
173
+
174
+ def safe_set(metric_name, func, *args, index=None):
175
+ if metric_name in metrics:
176
+ try:
177
+ value = func(*args)
178
+ if index is not None and isinstance(value, (list, tuple)):
179
+ setattr(backtest_metrics, metric_name, value[index])
180
+ else:
181
+ setattr(backtest_metrics, metric_name, value)
182
+ except OperationalException as e:
183
+ logger.warning(f"{metric_name} failed: {e}")
184
+
185
+ # Grouped metrics needing special handling
186
+ if "total_net_gain" in metrics or "total_net_gain_percentage" in metrics:
187
+ try:
188
+ total_return = get_total_return(backtest_run.portfolio_snapshots)
189
+ if "total_net_gain" in metrics:
190
+ backtest_metrics.total_net_gain = total_return[0]
191
+ if "total_net_gain_percentage" in metrics:
192
+ backtest_metrics.total_net_gain_percentage = total_return[1]
193
+ except OperationalException as e:
194
+ logger.warning(f"total_return failed: {e}")
195
+
196
+ if "total_growth" in metrics or "total_growth_percentage" in metrics:
197
+ try:
198
+ total_growth = get_total_growth(backtest_run.portfolio_snapshots)
199
+ if "total_growth" in metrics:
200
+ backtest_metrics.total_growth = total_growth[0]
201
+ if "total_growth_percentage" in metrics:
202
+ backtest_metrics.total_growth_percentage = total_growth[1]
203
+ except OperationalException as e:
204
+ logger.warning(f"total_growth failed: {e}")
205
+
206
+ if "total_loss" in metrics or "total_loss_percentage" in metrics:
207
+ try:
208
+ total_loss = get_total_loss(backtest_run.portfolio_snapshots)
209
+ if "total_loss" in metrics:
210
+ backtest_metrics.total_loss = total_loss[0]
211
+ if "total_loss_percentage" in metrics:
212
+ backtest_metrics.total_loss_percentage = total_loss[1]
213
+ except OperationalException as e:
214
+ logger.warning(f"total_loss failed: {e}")
215
+
216
+ if ("average_trade_return" in metrics
217
+ or "average_trade_return_percentage" in metrics):
218
+ try:
219
+ avg_return = get_average_trade_return(backtest_run.trades)
220
+ if "average_trade_return" in metrics:
221
+ backtest_metrics.average_trade_return = avg_return[0]
222
+ if "average_trade_return_percentage" in metrics:
223
+ backtest_metrics.average_trade_return_percentage = \
224
+ avg_return[1]
225
+ except OperationalException as e:
226
+ logger.warning(f"average_trade_return failed: {e}")
227
+
228
+ if ("average_trade_gain" in metrics
229
+ or "average_trade_gain_percentage" in metrics):
230
+ try:
231
+ avg_gain = get_average_trade_gain(backtest_run.trades)
232
+ if "average_trade_gain" in metrics:
233
+ backtest_metrics.average_trade_gain = avg_gain[0]
234
+ if "average_trade_gain_percentage" in metrics:
235
+ backtest_metrics.average_trade_gain_percentage = avg_gain[1]
236
+ except OperationalException as e:
237
+ logger.warning(f"average_trade_gain failed: {e}")
238
+
239
+ if ("average_trade_loss" in metrics
240
+ or "average_trade_loss_percentage" in metrics):
241
+ try:
242
+ avg_loss = get_average_trade_loss(backtest_run.trades)
243
+ if "average_trade_loss" in metrics:
244
+ backtest_metrics.average_trade_loss = avg_loss[0]
245
+ if "average_trade_loss_percentage" in metrics:
246
+ backtest_metrics.average_trade_loss_percentage = avg_loss[1]
247
+ except OperationalException as e:
248
+ logger.warning(f"average_trade_loss failed: {e}")
249
+
250
+ if ("current_average_trade_gain" in metrics
251
+ or "get_current_average_trade_gain_percentage" in metrics):
252
+ try:
253
+ current_avg_gain = get_current_average_trade_gain(
254
+ backtest_run.trades
255
+ )
256
+
257
+ if "current_average_trade_gain" in metrics:
258
+ backtest_metrics.current_average_trade_gain = \
259
+ current_avg_gain[0]
260
+
261
+ if "current_average_trade_gain_percentage" in metrics:
262
+ backtest_metrics.current_average_trade_gain_percentage = \
263
+ current_avg_gain[1]
264
+ except OperationalException as e:
265
+ logger.warning(f"current_average_trade_gain failed: {e}")
266
+
267
+ if ("current_average_trade_return" in metrics
268
+ or "current_average_trade_return_percentage" in metrics):
269
+ try:
270
+ current_avg_return = get_current_average_trade_return(
271
+ backtest_run.trades
272
+ )
273
+
274
+ if "current_average_trade_return" in metrics:
275
+ backtest_metrics.current_average_trade_return = \
276
+ current_avg_return[0]
277
+ if "current_average_trade_return_percentage" in metrics:
278
+ backtest_metrics.current_average_trade_return_percentage =\
279
+ current_avg_return[1]
280
+ except OperationalException as e:
281
+ logger.warning(f"current_average_trade_return failed: {e}")
282
+
283
+ if "current_average_trade_duration" in metrics:
284
+ try:
285
+ current_avg_duration = get_current_average_trade_duration(
286
+ backtest_run.trades, backtest_run
287
+ )
288
+ backtest_metrics.current_average_trade_duration = \
289
+ current_avg_duration
290
+ except OperationalException as e:
291
+ logger.warning(f"current_average_trade_duration failed: {e}")
292
+
293
+ if ("current_average_trade_loss" in metrics
294
+ or "current_average_trade_loss_percentage" in metrics):
295
+ try:
296
+ current_avg_loss = get_current_average_trade_loss(
297
+ backtest_run.trades
298
+ )
299
+ if "current_average_trade_loss" in metrics:
300
+ backtest_metrics.current_average_trade_loss = \
301
+ current_avg_loss[0]
302
+ if "current_average_trade_loss_percentage" in metrics:
303
+ backtest_metrics.current_average_trade_loss_percentage = \
304
+ current_avg_loss[1]
305
+ except OperationalException as e:
306
+ logger.warning(f"current_average_trade_loss failed: {e}")
307
+
308
+ safe_set("number_of_positive_trades", get_positive_trades, backtest_run.trades)
309
+ safe_set("percentage_positive_trades", get_positive_trades, backtest_run.trades, index=1)
310
+ safe_set("number_of_negative_trades", get_negative_trades, backtest_run.trades)
311
+ safe_set("percentage_negative_trades", get_negative_trades, backtest_run.trades, index=1)
312
+ safe_set("median_trade_return", get_median_trade_return, backtest_run.trades, index=0)
313
+ safe_set("median_trade_return_percentage", get_median_trade_return, backtest_run.trades, index=1)
314
+ safe_set("number_of_trades", get_number_of_trades, backtest_run.trades)
315
+ safe_set("number_of_trades_closed", get_number_of_closed_trades, backtest_run.trades)
316
+ safe_set("number_of_trades_opened", get_number_of_open_trades, backtest_run.trades)
317
+ safe_set("average_trade_duration", get_average_trade_duration, backtest_run.trades)
318
+ safe_set("average_trade_size", get_average_trade_size, backtest_run.trades)
319
+ safe_set("equity_curve", get_equity_curve, backtest_run.portfolio_snapshots)
320
+ safe_set("final_value", get_final_value, backtest_run.portfolio_snapshots)
321
+ safe_set("cagr", get_cagr, backtest_run.portfolio_snapshots)
322
+ safe_set("sharpe_ratio", get_sharpe_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
323
+ safe_set("rolling_sharpe_ratio", get_rolling_sharpe_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
324
+ safe_set("sortino_ratio", get_sortino_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
325
+ safe_set("profit_factor", get_profit_factor, backtest_run.trades)
326
+ safe_set("calmar_ratio", get_calmar_ratio, backtest_run.portfolio_snapshots)
327
+ safe_set("annual_volatility", get_annual_volatility, backtest_run.portfolio_snapshots)
328
+ safe_set("monthly_returns", get_monthly_returns, backtest_run.portfolio_snapshots)
329
+ safe_set("yearly_returns", get_yearly_returns, backtest_run.portfolio_snapshots)
330
+ safe_set("drawdown_series", get_drawdown_series, backtest_run.portfolio_snapshots)
331
+ safe_set("max_drawdown", get_max_drawdown, backtest_run.portfolio_snapshots)
332
+ safe_set("max_drawdown_absolute", get_max_drawdown_absolute, backtest_run.portfolio_snapshots)
333
+ safe_set("max_daily_drawdown", get_max_daily_drawdown, backtest_run.portfolio_snapshots)
334
+ safe_set("max_drawdown_duration", get_max_drawdown_duration, backtest_run.portfolio_snapshots)
335
+ safe_set("trades_per_year", get_trades_per_year, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
336
+ safe_set("trades_per_day", get_trades_per_day, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
337
+ safe_set("exposure_ratio", get_exposure_ratio, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
338
+ safe_set("cumulative_exposure", get_cumulative_exposure, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
339
+ safe_set("best_trade", get_best_trade, backtest_run.trades)
340
+ safe_set("worst_trade", get_worst_trade, backtest_run.trades)
341
+ safe_set("win_rate", get_win_rate, backtest_run.trades)
342
+ safe_set("current_win_rate", get_current_win_rate, backtest_run.trades)
343
+ safe_set("win_loss_ratio", get_win_loss_ratio, backtest_run.trades)
344
+ safe_set("current_win_loss_ratio", get_current_win_loss_ratio, backtest_run.trades)
345
+ safe_set("percentage_winning_months", get_percentage_winning_months, backtest_run.portfolio_snapshots)
346
+ safe_set("percentage_winning_years", get_percentage_winning_years, backtest_run.portfolio_snapshots)
347
+ safe_set("average_monthly_return", get_average_monthly_return, backtest_run.portfolio_snapshots)
348
+ safe_set("average_monthly_return_winning_months", get_average_monthly_return_winning_months, backtest_run.portfolio_snapshots)
349
+ safe_set("average_monthly_return_losing_months", get_average_monthly_return_losing_months, backtest_run.portfolio_snapshots)
350
+ safe_set("best_month", get_best_month, backtest_run.portfolio_snapshots)
351
+ safe_set("best_year", get_best_year, backtest_run.portfolio_snapshots)
352
+ safe_set("worst_month", get_worst_month, backtest_run.portfolio_snapshots)
353
+ safe_set("worst_year", get_worst_year, backtest_run.portfolio_snapshots)
354
+ safe_set("gross_loss", get_gross_loss, backtest_run.trades)
355
+ safe_set("gross_profit", get_gross_profit, backtest_run.trades)
356
+ safe_set("cumulative_return_series", get_cumulative_return_series, backtest_run.portfolio_snapshots)
357
+ safe_set("cumulative_return", get_cumulative_return, backtest_run.portfolio_snapshots)
358
+ return backtest_metrics
@@ -0,0 +1,83 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from .cagr import get_cagr
5
+
6
+
7
+ def get_mean_daily_return(snapshots):
8
+ """
9
+ Calculate the mean daily return from the total value of the snapshots.
10
+
11
+ This function computes the mean daily return based on the list of
12
+ snapshots in the report. If the snapshots have a granularity of less
13
+ than a day, the function will resample to daily frequency and compute
14
+ average daily returns.
15
+
16
+ If there is less data then for a year, it will use cagr to
17
+ calculate the mean daily return.
18
+
19
+ Args:
20
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
21
+
22
+ Returns:
23
+ float: The mean daily return.
24
+ """
25
+
26
+ if len(snapshots) < 2:
27
+ return 0.0 # Not enough data
28
+
29
+ # Create DataFrame from snapshots
30
+ data = [(s.created_at, s.total_value) for s in snapshots]
31
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
32
+ df['created_at'] = pd.to_datetime(df['created_at'])
33
+ df = df.sort_values('created_at').drop_duplicates('created_at')\
34
+ .set_index('created_at')
35
+
36
+ start_date = df.iloc[0].name
37
+ end_date = df.iloc[-1].name
38
+
39
+ # Check if the period is less than a year
40
+ if (end_date - start_date).days < 365:
41
+ # Use CAGR to calculate mean daily return
42
+ cagr = get_cagr(snapshots)
43
+ if cagr == 0.0:
44
+ return 0.0
45
+ return (1 + cagr) ** (1 / 365) - 1
46
+
47
+ # Resample to daily frequency using last value of the day
48
+ daily_df = df.resample('1D').last().dropna()
49
+
50
+ # Calculate daily returns
51
+ daily_df['return'] = daily_df['total_value'].pct_change()
52
+ daily_df = daily_df.dropna()
53
+
54
+ if daily_df.empty:
55
+ return 0.0
56
+
57
+ mean_return = daily_df['return'].mean()
58
+
59
+ if np.isnan(mean_return):
60
+ return 0.0
61
+
62
+ return mean_return
63
+
64
+
65
+ def get_mean_yearly_return(report, periods_per_year=365):
66
+ """
67
+ Calculate the mean yearly return from a backtest report by
68
+ annualizing the mean daily return.
69
+
70
+ Args:
71
+ report (BacktestReport): The report containing the snapshots.
72
+ periods_per_year (int): Number of periods in a year (e.g., 365 for daily data).
73
+
74
+ Returns:
75
+ float: The mean yearly return (annualized).
76
+ """
77
+ mean_daily_return = get_mean_daily_return(report)
78
+
79
+ if mean_daily_return == 0.0:
80
+ return 0.0
81
+
82
+ return (1 + mean_daily_return) ** periods_per_year - 1
83
+
@@ -0,0 +1,57 @@
1
+ import logging
2
+ from pandas import DataFrame, DatetimeIndex
3
+ from investing_algorithm_framework.domain.exceptions import \
4
+ OperationalException
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def get_price_efficiency_ratio(data: DataFrame):
10
+ """
11
+ Calculate the price efficiency ratio (noise) for each symbol.
12
+
13
+ The price efficiency ratio is calculated as follows:
14
+
15
+ 1. Calculate the net price change over the period
16
+ 2. Calculate the sum of absolute daily price changes
17
+ 3. Calculate Efficiency Ratio = Net Price Change / Sum of Absolute
18
+ Daily Price Changes
19
+
20
+ The price efficiency ratio is a measure of the efficiency of the
21
+ price movement over the period. A higher efficiency ratio indicates
22
+ a more efficient price movement.
23
+
24
+ Args:
25
+ data (dict): A pandas DataFrame containing a column with either a
26
+ 'Close' or 'Price' label and a datetime index.
27
+
28
+ returns:
29
+ float: The price efficiency ratio
30
+ """
31
+
32
+ # Check if close value and index is a datetime object
33
+ if 'Close' not in data.columns:
34
+ raise OperationalException(
35
+ "Close column not found in data, "
36
+ "required for price efficiency ratio calculation"
37
+ )
38
+
39
+ if not isinstance(data.index, DatetimeIndex):
40
+ raise OperationalException(
41
+ "Index is not a datetime object,"
42
+ "required for price efficiency ratio calculation"
43
+ )
44
+
45
+ # Calculate daily price changes
46
+ data['Daily Change'] = data['Close'].diff()
47
+
48
+ # Calculate net price change over the period
49
+ net_price_change = abs(
50
+ data['Close'].iloc[-1] - data['Close'].iloc[0])
51
+
52
+ # Calculate the sum of absolute daily price changes
53
+ sum_absolute_changes = data['Daily Change'] \
54
+ .abs().sum()
55
+
56
+ # Calculate Efficiency Ratio
57
+ return net_price_change / sum_absolute_changes
@@ -0,0 +1,165 @@
1
+ """
2
+ Module for calculating profit factor metrics from backtest trades.
3
+
4
+ | **Profit Factor** | **Interpretation** |
5
+ | ----------------- | ----------------------------------------------------------------------------------------- |
6
+ | **< 1.0** | **Losing strategy** — losses outweigh profits |
7
+ | **1.0 – 1.3** | Weak or barely breakeven — needs improvement or may not be sustainable |
8
+ | **1.3 – 1.6** | Average — possibly profitable but sensitive to market regime changes |
9
+ | **1.6 – 2.0** | Good — generally indicates a solid, sustainable edge |
10
+ | **2.0 – 3.0** | Very good — strong edge with lower drawdown risk |
11
+ | **> 3.0** | Excellent — rare in real markets; often associated with low-frequency or niche strategies |
12
+
13
+ """
14
+
15
+ from collections import deque
16
+ from datetime import datetime
17
+ from typing import List, Tuple
18
+
19
+ from investing_algorithm_framework.domain.models import Trade
20
+
21
+
22
+ def get_cumulative_profit_factor_series(
23
+ trades: List[Trade]
24
+ ) -> list[tuple[datetime, float]]:
25
+ """
26
+ Calculates the cumulative profit factor over time from a backtest report.
27
+
28
+ Args:
29
+ trades (List[Trade]): List of closed trades from the backtest report.
30
+
31
+ Returns:
32
+ List of (datetime, float) tuples: (timestamp, cumulative profit factor)
33
+ """
34
+ results = []
35
+ gross_profit = 0.0
36
+ gross_loss = 0.0
37
+
38
+ for trade in trades:
39
+ close_time = trade.closed_at
40
+ profit = trade.net_gain
41
+
42
+ if profit >= 0:
43
+ gross_profit += profit
44
+ else:
45
+ gross_loss += abs(profit)
46
+
47
+ # Calculate profit factor with division-by-zero protection
48
+ if gross_loss > 0:
49
+ profit_factor = gross_profit / gross_loss
50
+ else:
51
+ profit_factor = float('inf') if gross_profit > 0 else 0.0
52
+
53
+ results.append((close_time, profit_factor))
54
+
55
+ return results
56
+
57
+
58
+ def get_rolling_profit_factor_series(
59
+ trades: List[Trade], window_size: int = 20
60
+ ) -> List[Tuple[datetime, float]]:
61
+ """
62
+ Calculates the rolling profit factor over time from a backtest report.
63
+
64
+ The rolling profit factor is computed using the most recent
65
+ `window_size` trades and updated after each closed trade.
66
+
67
+ Args:
68
+ backtest_report (BacktestReport): A instance of BacktestReport
69
+ containing closed trades.
70
+ window_size: The number of most recent trades to include in
71
+ each rolling calculation.
72
+
73
+ Returns:
74
+ A list of tuples, where each tuple contains:
75
+ - datetime: The close time of the trade (or aligned date).
76
+ - float: The rolling profit factor at that time.
77
+ """
78
+
79
+ results = []
80
+ trade_window = deque(maxlen=window_size)
81
+
82
+ for trade in trades:
83
+ close_time = trade.closed_at
84
+ profit = trade.net_gain
85
+
86
+ trade_window.append(profit)
87
+
88
+ gross_profit = sum(p for p in trade_window if p >= 0)
89
+ gross_loss = sum(abs(p) for p in trade_window if p < 0)
90
+
91
+ if gross_loss > 0:
92
+ profit_factor = gross_profit / gross_loss
93
+ else:
94
+ profit_factor = float('inf') if gross_profit > 0 else 0.0
95
+
96
+ results.append((close_time, profit_factor))
97
+
98
+ return results
99
+
100
+
101
+ def get_profit_factor(trades: List[Trade]) -> float:
102
+ """
103
+ Calculates the total profit factor at the end of the backtest.
104
+
105
+ The profit factor is defined as:
106
+ Total Gross Profit / Total Gross Loss
107
+
108
+ Args:
109
+ trades (List[Trade]): List of closed trades from the backtest report.
110
+
111
+ Returns:
112
+ float: The profit factor at the end of the backtest.
113
+ Returns float('inf') if there are no losses,
114
+ and 0.0 if there are no profits and losses.
115
+ """
116
+ gross_profit = 0.0
117
+ gross_loss = 0.0
118
+
119
+ for trade in trades:
120
+ profit = trade.net_gain
121
+ if profit > 0:
122
+ gross_profit += profit
123
+ elif profit < 0:
124
+ gross_loss += abs(profit)
125
+
126
+ if gross_loss == 0:
127
+ return float('inf') if gross_profit > 0 else 0.0
128
+
129
+ return gross_profit / gross_loss
130
+
131
+
132
+ def get_gross_profit(trades: List[Trade]) -> float:
133
+ """
134
+ Function to calculate the total gross profit from a list of trades.
135
+
136
+ Args:
137
+ trades (List[Trade]): List of closed trades from the backtest report.
138
+
139
+ Returns:
140
+ float: The total gross profit from the trades.
141
+ """
142
+
143
+ gross_profit = 0.0
144
+ for trade in trades:
145
+ if trade.net_gain > 0:
146
+ gross_profit += trade.net_gain
147
+ return gross_profit
148
+
149
+
150
+ def get_gross_loss(trades: List[Trade]) -> float:
151
+ """
152
+ Function to calculate the total gross loss from a list of trades.
153
+
154
+ Args:
155
+ trades (List[Trade]): List of closed trades from the backtest report.
156
+
157
+ Returns:
158
+ float: The total gross loss from the trades.
159
+ """
160
+
161
+ gross_loss = 0.0
162
+ for trade in trades:
163
+ if trade.net_gain < 0:
164
+ gross_loss += abs(trade.net_gain)
165
+ return gross_loss