investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__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 (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
@@ -3,10 +3,9 @@ from .sortino_ratio import get_sortino_ratio
3
3
  from .drawdown import get_drawdown_series, get_max_drawdown
4
4
  from .equity_curve import get_equity_curve
5
5
  from .price_efficiency import get_price_efficiency_ratio
6
- from .sharp_ratio import get_sharpe_ratio
7
6
  from .profit_factor import get_profit_factor, \
8
7
  get_cumulative_profit_factor_series, get_rolling_profit_factor_series
9
- from .sharp_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
8
+ from .sharpe_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
10
9
  from .price_efficiency import get_price_efficiency_ratio
11
10
  from .equity_curve import get_equity_curve
12
11
  from .drawdown import get_drawdown_series, get_max_drawdown, \
@@ -16,17 +15,29 @@ from .cagr import get_cagr
16
15
  from .standard_deviation import get_standard_deviation_downside_returns, \
17
16
  get_standard_deviation_returns
18
17
  from .returns import get_yearly_returns, get_monthly_returns, \
19
- get_best_year, get_best_month, get_worst_month, get_best_trade, \
20
- get_worst_trade, get_total_return, get_average_yearly_return, \
21
- get_average_gain, get_average_loss, get_average_monthly_return, \
18
+ get_best_year, get_best_month, get_worst_month, get_total_return, \
19
+ get_average_yearly_return, get_average_monthly_return, \
22
20
  get_percentage_winning_months, get_average_monthly_return_losing_months, \
23
- get_average_monthly_return_winning_months, get_best_trade_date, \
24
- get_worst_trade_date, get_percentage_winning_years, \
25
- get_worst_year
26
- from .exposure import get_exposure, get_average_trade_duration, \
27
- get_trade_frequency, get_trades_per_day, get_trades_per_year
28
- from .win_rate import get_win_rate, get_win_loss_ratio
21
+ get_average_monthly_return_winning_months, get_total_growth, \
22
+ get_percentage_winning_years, get_worst_year, get_cumulative_return, \
23
+ get_total_loss, get_cumulative_return_series
24
+ from .exposure import get_average_trade_duration, \
25
+ get_trade_frequency, get_trades_per_day, get_trades_per_year, \
26
+ get_cumulative_exposure, get_exposure_ratio
27
+ from .win_rate import get_win_rate, get_win_loss_ratio, get_current_win_rate, \
28
+ get_current_win_loss_ratio
29
29
  from .calmar_ratio import get_calmar_ratio
30
+ from .generate import create_backtest_metrics, \
31
+ create_backtest_metrics_for_backtest
32
+ from .risk_free_rate import get_risk_free_rate_us
33
+ from .trades import get_negative_trades, get_positive_trades, \
34
+ get_number_of_trades, get_number_of_closed_trades, \
35
+ get_average_trade_size, get_average_trade_return, get_best_trade, \
36
+ get_worst_trade, get_average_trade_gain, get_median_trade_return, \
37
+ get_average_trade_loss, get_current_average_trade_loss, \
38
+ get_current_average_trade_duration, get_current_average_trade_gain, \
39
+ get_current_average_trade_return, get_number_of_open_trades, \
40
+ get_average_trade_duration
30
41
 
31
42
  __all__ = [
32
43
  "get_annual_volatility",
@@ -45,8 +56,10 @@ __all__ = [
45
56
  "get_standard_deviation_downside_returns",
46
57
  "get_max_drawdown_absolute",
47
58
  "get_total_return",
48
- "get_exposure",
49
- "get_average_trade_duration",
59
+ "get_total_loss",
60
+ "get_total_growth",
61
+ "get_cumulative_exposure",
62
+ "get_exposure_ratio",
50
63
  "get_win_rate",
51
64
  "get_win_loss_ratio",
52
65
  "get_calmar_ratio",
@@ -60,8 +73,6 @@ __all__ = [
60
73
  "get_best_trade",
61
74
  "get_worst_trade",
62
75
  "get_average_yearly_return",
63
- "get_average_gain",
64
- "get_average_loss",
65
76
  "get_average_monthly_return",
66
77
  "get_percentage_winning_months",
67
78
  "get_average_trade_duration",
@@ -76,8 +87,28 @@ __all__ = [
76
87
  "get_trades_per_year",
77
88
  "get_average_monthly_return_losing_months",
78
89
  "get_average_monthly_return_winning_months",
79
- "get_best_trade_date",
80
- "get_worst_trade_date",
81
90
  "get_percentage_winning_years",
82
91
  "get_rolling_sharpe_ratio",
92
+ "create_backtest_metrics",
93
+ "get_risk_free_rate_us",
94
+ "get_median_trade_return",
95
+ "get_average_trade_gain",
96
+ "get_average_trade_loss",
97
+ "get_average_trade_size",
98
+ "get_average_trade_return",
99
+ "get_number_of_trades",
100
+ "get_number_of_closed_trades",
101
+ "get_negative_trades",
102
+ "get_positive_trades",
103
+ "get_cumulative_return",
104
+ "get_cumulative_return_series",
105
+ "get_current_win_rate",
106
+ "get_current_win_loss_ratio",
107
+ "get_current_average_trade_loss",
108
+ "get_current_average_trade_duration",
109
+ "get_current_average_trade_gain",
110
+ "get_current_average_trade_return",
111
+ "get_number_of_open_trades",
112
+ "get_average_trade_duration",
113
+ "create_backtest_metrics_for_backtest"
83
114
  ]
@@ -13,11 +13,11 @@ peak-to-trough decline of a portfolio:
13
13
  from typing import List, Tuple
14
14
  import pandas as pd
15
15
  from datetime import datetime
16
- from investing_algorithm_framework.domain import PortfolioSnapshot
16
+ from investing_algorithm_framework.domain import PortfolioSnapshot, Trade
17
17
  from .equity_curve import get_equity_curve
18
18
 
19
19
 
20
- def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[datetime, float]]:
20
+ def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[float, datetime]]:
21
21
  """
22
22
  Calculate the drawdown series of a backtest report.
23
23
 
@@ -39,12 +39,12 @@ def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[dateti
39
39
  drawdown_series = []
40
40
  max_value = None
41
41
 
42
- for timestamp, value in equity_curve:
42
+ for value, timestamp in equity_curve:
43
43
  if max_value is None:
44
44
  max_value = value
45
45
  max_value = max(max_value, value)
46
46
  drawdown = (value - max_value) / max_value # This will be <= 0
47
- drawdown_series.append((timestamp, drawdown))
47
+ drawdown_series.append((drawdown, timestamp))
48
48
 
49
49
  return drawdown_series
50
50
 
@@ -69,10 +69,10 @@ def get_max_drawdown(snapshots: List[PortfolioSnapshot]) -> float:
69
69
  if not equity_curve:
70
70
  return 0.0
71
71
 
72
- peak = equity_curve[0][1]
72
+ peak = equity_curve[0][0]
73
73
  max_drawdown_pct = 0.0
74
74
 
75
- for _, equity in equity_curve:
75
+ for equity, _ in equity_curve:
76
76
  if equity > peak:
77
77
  peak = equity
78
78
 
@@ -134,11 +134,11 @@ def get_max_drawdown_duration(snapshots: List[PortfolioSnapshot]) -> int:
134
134
  if not equity_curve:
135
135
  return 0
136
136
 
137
- peak = equity_curve[0][1]
137
+ peak = equity_curve[0][0]
138
138
  max_duration = 0
139
139
  current_duration = 0
140
140
 
141
- for _, equity in equity_curve:
141
+ for equity, _ in equity_curve:
142
142
  if equity < peak:
143
143
  current_duration += 1
144
144
  else:
@@ -168,10 +168,10 @@ def get_max_drawdown_absolute(snapshots: List[PortfolioSnapshot]) -> float:
168
168
  if not equity_curve:
169
169
  return 0.0
170
170
 
171
- peak = equity_curve[0][1]
171
+ peak = equity_curve[0][0]
172
172
  max_drawdown = 0.0
173
173
 
174
- for _, equity in equity_curve:
174
+ for equity, _ in equity_curve:
175
175
  if equity > peak:
176
176
  peak = equity
177
177
 
@@ -5,7 +5,7 @@ from investing_algorithm_framework.domain import PortfolioSnapshot
5
5
 
6
6
  def get_equity_curve(
7
7
  snapshots: List[PortfolioSnapshot]
8
- ) -> list[tuple[datetime, float]]:
8
+ ) -> list[tuple[float, datetime]]:
9
9
  """
10
10
  Calculate the total size of the portfolio at each snapshot timestamp.
11
11
 
@@ -19,6 +19,6 @@ def get_equity_curve(
19
19
  for snapshot in snapshots:
20
20
  timestamp = snapshot.created_at
21
21
  total_size = snapshot.total_value
22
- series.append((timestamp, total_size))
22
+ series.append((total_size, timestamp))
23
23
 
24
24
  return series
@@ -6,12 +6,70 @@ Exposure around 1 means capital is nearly fully invested most of the time, but n
6
6
  Low exposure (<1) means capital is mostly idle or only partially invested.
7
7
  """
8
8
 
9
+ from datetime import datetime, timedelta
9
10
  from typing import List
10
- from datetime import timedelta, datetime
11
+
11
12
  from investing_algorithm_framework.domain import Trade
12
13
 
13
14
 
14
- def get_exposure(
15
+ def get_exposure_ratio(
16
+ trades: List["Trade"], start_date: datetime, end_date: datetime
17
+ ) -> float:
18
+ """
19
+ Calculates the exposure ratio (time in market) as the fraction of the total
20
+ backtest duration where at least one position was open.
21
+
22
+ Unlike cumulative exposure, overlapping trades are not double-counted.
23
+ The result is always between 0 and 1.
24
+
25
+ Args:
26
+ trades (List[Trade]): List of trades executed during the backtest.
27
+ start_date (datetime): The start date of the backtest.
28
+ end_date (datetime): The end date of the backtest.
29
+
30
+ Returns:
31
+ A float between 0 and 1 representing the exposure ratio.
32
+ """
33
+ if not trades:
34
+ return 0.0
35
+
36
+ # Collect trade intervals
37
+ intervals = []
38
+ for trade in trades:
39
+ entry = max(trade.opened_at, start_date)
40
+ exit = min(trade.closed_at or end_date, end_date)
41
+ if exit > entry:
42
+ intervals.append((entry, exit))
43
+
44
+ if not intervals:
45
+ return 0.0
46
+
47
+ # Sort intervals by start time
48
+ intervals.sort(key=lambda x: x[0])
49
+
50
+ # Merge overlapping intervals
51
+ merged = []
52
+ current_start, current_end = intervals[0]
53
+ for start, end in intervals[1:]:
54
+ if start <= current_end: # overlap
55
+ current_end = max(current_end, end)
56
+ else:
57
+ merged.append((current_start, current_end))
58
+ current_start, current_end = start, end
59
+ merged.append((current_start, current_end))
60
+
61
+ # Total time with at least one open trade
62
+ total_exposed_time = sum((end - start for start, end in merged), timedelta(0))
63
+
64
+ backtest_duration = end_date - start_date
65
+ if backtest_duration.total_seconds() == 0:
66
+ return 0.0
67
+
68
+ return total_exposed_time.total_seconds() \
69
+ / backtest_duration.total_seconds()
70
+
71
+
72
+ def get_cumulative_exposure(
15
73
  trades: List[Trade], start_date: datetime, end_date: datetime
16
74
  ) -> float:
17
75
  """
@@ -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
@@ -127,3 +127,39 @@ def get_profit_factor(trades: List[Trade]) -> float:
127
127
  return float('inf') if gross_profit > 0 else 0.0
128
128
 
129
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
@@ -40,7 +40,7 @@ from typing import List
40
40
 
41
41
  from investing_algorithm_framework.domain import PortfolioSnapshot
42
42
  from .drawdown import get_max_drawdown_absolute
43
- from .returns import get_total_net_gain
43
+ from .returns import get_total_return
44
44
 
45
45
 
46
46
  def get_recovery_factor(snapshots: List[PortfolioSnapshot]) -> float:
@@ -67,7 +67,7 @@ def get_recovery_factor(snapshots: List[PortfolioSnapshot]) -> float:
67
67
  return 0.0
68
68
 
69
69
  max_drawdown_absolute = get_max_drawdown_absolute(snapshots)
70
- net_profit = get_total_net_gain(snapshots)
70
+ net_profit, _ = get_total_return(snapshots)
71
71
 
72
72
  if max_drawdown_absolute == 0:
73
73
  return float('inf') if net_profit > 0 else 0.0