investing-algorithm-framework 3.7.0__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 (256) hide show
  1. investing_algorithm_framework/__init__.py +168 -45
  2. investing_algorithm_framework/app/__init__.py +32 -1
  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 +1933 -589
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1725 -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/action_handlers/__init__.py +4 -2
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/strategy.py +664 -84
  41. investing_algorithm_framework/app/task.py +5 -3
  42. investing_algorithm_framework/app/web/__init__.py +2 -1
  43. investing_algorithm_framework/app/web/create_app.py +4 -2
  44. investing_algorithm_framework/cli/__init__.py +0 -0
  45. investing_algorithm_framework/cli/cli.py +226 -0
  46. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  47. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  48. investing_algorithm_framework/cli/initialize_app.py +603 -0
  49. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  50. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  51. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  52. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  53. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  55. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  56. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  58. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  59. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  60. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  61. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  62. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  63. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  64. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  65. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  66. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  67. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  68. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  69. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  70. investing_algorithm_framework/create_app.py +40 -6
  71. investing_algorithm_framework/dependency_container.py +72 -56
  72. investing_algorithm_framework/domain/__init__.py +71 -47
  73. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  74. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  75. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  76. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  77. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  78. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  79. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  81. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  82. investing_algorithm_framework/domain/config.py +59 -91
  83. investing_algorithm_framework/domain/constants.py +13 -38
  84. investing_algorithm_framework/domain/data_provider.py +334 -0
  85. investing_algorithm_framework/domain/data_structures.py +3 -2
  86. investing_algorithm_framework/domain/exceptions.py +51 -1
  87. investing_algorithm_framework/domain/models/__init__.py +17 -12
  88. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  89. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  90. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  91. investing_algorithm_framework/domain/models/event.py +35 -0
  92. investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
  93. investing_algorithm_framework/domain/models/order/order.py +77 -83
  94. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  95. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  96. investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
  97. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
  98. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  99. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  100. investing_algorithm_framework/domain/models/position/position.py +12 -0
  101. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  102. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  104. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  105. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  106. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  107. investing_algorithm_framework/domain/models/time_frame.py +37 -0
  108. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  109. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  110. investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
  111. investing_algorithm_framework/domain/models/trade/trade.py +295 -171
  112. investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
  113. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  114. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  115. investing_algorithm_framework/domain/order_executor.py +112 -0
  116. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  117. investing_algorithm_framework/domain/services/__init__.py +2 -9
  118. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
  119. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  120. investing_algorithm_framework/domain/strategy.py +1 -29
  121. investing_algorithm_framework/domain/utils/__init__.py +12 -7
  122. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  123. investing_algorithm_framework/domain/utils/dates.py +57 -0
  124. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  125. investing_algorithm_framework/domain/utils/polars.py +53 -0
  126. investing_algorithm_framework/domain/utils/random.py +29 -0
  127. investing_algorithm_framework/download_data.py +108 -0
  128. investing_algorithm_framework/infrastructure/__init__.py +31 -18
  129. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  130. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  131. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  132. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  133. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  134. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  135. investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
  136. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
  137. investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
  138. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  139. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  140. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  141. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
  142. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
  143. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  144. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  145. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  146. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  147. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  148. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  149. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  150. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  151. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  152. investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
  153. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  154. investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
  155. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  156. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  157. investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
  158. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  159. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  160. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  161. investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
  162. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  163. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  164. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  165. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  166. investing_algorithm_framework/services/__init__.py +113 -16
  167. investing_algorithm_framework/services/backtesting/__init__.py +0 -7
  168. investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
  169. investing_algorithm_framework/services/configuration_service.py +77 -11
  170. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  171. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  172. investing_algorithm_framework/services/market_credential_service.py +16 -1
  173. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  174. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  175. investing_algorithm_framework/services/metrics/beta.py +0 -0
  176. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  177. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  178. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  179. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  180. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  181. investing_algorithm_framework/services/metrics/generate.py +358 -0
  182. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  183. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  184. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  185. investing_algorithm_framework/services/metrics/returns.py +452 -0
  186. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  187. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  188. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  189. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  190. investing_algorithm_framework/services/metrics/trades.py +500 -0
  191. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  192. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  193. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  194. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  195. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  196. investing_algorithm_framework/services/order_service/__init__.py +3 -1
  197. investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
  198. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  199. investing_algorithm_framework/services/order_service/order_service.py +407 -326
  200. investing_algorithm_framework/services/portfolios/__init__.py +3 -1
  201. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
  202. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
  203. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  204. investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
  205. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
  206. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
  207. investing_algorithm_framework/services/positions/__init__.py +7 -0
  208. investing_algorithm_framework/services/positions/position_service.py +210 -0
  209. investing_algorithm_framework/services/repository_service.py +8 -2
  210. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  211. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  212. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  213. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  214. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  215. investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
  216. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  217. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  218. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  219. investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
  220. investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
  221. investing_algorithm_framework/app/algorithm.py +0 -1105
  222. investing_algorithm_framework/domain/graphs.py +0 -382
  223. investing_algorithm_framework/domain/metrics/__init__.py +0 -6
  224. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
  225. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
  226. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  227. investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
  228. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
  229. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  230. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  231. investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
  232. investing_algorithm_framework/domain/services/market_service.py +0 -153
  233. investing_algorithm_framework/domain/singleton.py +0 -9
  234. investing_algorithm_framework/domain/utils/backtesting.py +0 -472
  235. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
  236. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
  237. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
  238. investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
  239. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  240. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
  241. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  242. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  243. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
  244. investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
  245. investing_algorithm_framework/services/backtesting/graphs.py +0 -61
  246. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
  247. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
  248. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
  249. investing_algorithm_framework/services/position_service.py +0 -31
  250. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
  251. investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
  252. investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
  253. /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
  254. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  255. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  256. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
@@ -0,0 +1,500 @@
1
+ from typing import List, Tuple
2
+
3
+ from investing_algorithm_framework.domain import Trade, TradeStatus, \
4
+ OperationalException, BacktestRun
5
+
6
+
7
+ def get_positive_trades(
8
+ trades: List[Trade]
9
+ ) -> Tuple[int, float]:
10
+ """
11
+ Calculate the number and percentage of positive trades.
12
+
13
+ Args:
14
+ trades (List[Trade]): List of Trade objects.
15
+
16
+ Returns:
17
+ Tuple[int, float]: A tuple containing the number of positive trades
18
+ and the percentage of positive trades.
19
+ """
20
+ if trades is None or len(trades) == 0:
21
+ raise OperationalException(
22
+ "Trades list is empty or None, cannot compute positive trades."
23
+ )
24
+
25
+ closed_trades = [
26
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
27
+ ]
28
+
29
+ positive_trades = [
30
+ trade for trade in closed_trades if trade.net_gain_absolute > 0
31
+ ]
32
+ number_of_positive_trades = len(positive_trades)
33
+ percentage_positive_trades = (
34
+ (number_of_positive_trades / len(closed_trades)) * 100.0
35
+ if len(closed_trades) > 0 else 0.0
36
+ )
37
+ return number_of_positive_trades, percentage_positive_trades
38
+
39
+
40
+ def get_negative_trades(
41
+ trades: List[Trade]
42
+ ) -> Tuple[int, float]:
43
+ """
44
+ Calculate the number and percentage of negative trades.
45
+
46
+ Args:
47
+ trades (List[Trade]): List of Trade objects.
48
+
49
+ Returns:
50
+ Tuple[int, float]: A tuple containing the number of negative trades
51
+ and the percentage of negative trades.
52
+ """
53
+ if trades is None or len(trades) == 0:
54
+ raise OperationalException(
55
+ "Trades list is empty or None, cannot compute negative trades."
56
+ )
57
+
58
+ closed_trades = [
59
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
60
+ ]
61
+
62
+ negative_trades = [
63
+ trade for trade in closed_trades if trade.net_gain_absolute < 0
64
+ ]
65
+ number_of_negative_trades = len(negative_trades)
66
+ percentage_negative_trades = (
67
+ (number_of_negative_trades / len(closed_trades)) * 100.0
68
+ if len(closed_trades) > 0 else 0.0
69
+ )
70
+ return number_of_negative_trades, percentage_negative_trades
71
+
72
+
73
+ def get_number_of_trades(
74
+ trades: List[Trade]
75
+ ) -> int:
76
+ """
77
+ Calculate the total number of trades.
78
+
79
+ Args:
80
+ trades (List[Trade]): List of Trade objects.
81
+
82
+ Returns:
83
+ int: The total number of trades.
84
+ """
85
+ if trades is None or len(trades) == 0:
86
+ raise OperationalException(
87
+ "Trades list is None, cannot compute number of trades."
88
+ )
89
+ return len(trades)
90
+
91
+
92
+ def get_number_of_open_trades(
93
+ trades: List[Trade]
94
+ ) -> int:
95
+ """
96
+ Calculate the number of open trades.
97
+
98
+ Args:
99
+ trades (List[Trade]): List of Trade objects.
100
+
101
+ Returns:
102
+ int: The number of open trades.
103
+ """
104
+ if trades is None or len(trades) == 0:
105
+ raise OperationalException(
106
+ "Trades list is None, cannot compute number of open trades."
107
+ )
108
+
109
+ open_trades = [
110
+ trade for trade in trades if TradeStatus.OPEN.equals(trade.status)
111
+ ]
112
+ return len(open_trades)
113
+
114
+
115
+ def get_number_of_closed_trades(
116
+ trades: List[Trade]
117
+ ) -> int:
118
+ """
119
+ Calculate the number of closed trades.
120
+
121
+ Args:
122
+ trades (List[Trade]): List of Trade objects.
123
+
124
+ Returns:
125
+ int: The number of closed trades.
126
+ """
127
+ if trades is None or len(trades) == 0:
128
+ raise OperationalException(
129
+ "Trades list is None, cannot compute number of closed trades."
130
+ )
131
+
132
+ closed_trades = [
133
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
134
+ ]
135
+ return len(closed_trades)
136
+
137
+
138
+ def get_average_trade_duration(
139
+ trades: List[Trade]
140
+ ) -> float:
141
+ """
142
+ Calculate the average duration of closed trades in hours.
143
+
144
+ Args:
145
+ trades (List[Trade]): List of Trade objects.
146
+
147
+ Returns:
148
+ float: The average trade duration in hours.
149
+ """
150
+ if trades is None or len(trades) == 0:
151
+ raise OperationalException(
152
+ "Trades list is None, cannot compute average trade duration."
153
+ )
154
+
155
+ total_duration = 0.0
156
+
157
+ for trade in trades:
158
+ if TradeStatus.CLOSED.equals(trade.status):
159
+ total_duration += (trade.closed_at - trade.opened_at)\
160
+ .total_seconds() / 3600
161
+
162
+ number_of_trades = get_number_of_closed_trades(trades)
163
+ return total_duration / number_of_trades if number_of_trades > 0 else 0.0
164
+
165
+
166
+ def get_current_average_trade_duration(
167
+ trades: List[Trade], backtest_run: BacktestRun
168
+ ) -> float:
169
+ """
170
+ Calculate the average duration of currently closed and open trades
171
+ in hours.
172
+
173
+ Args:
174
+ trades (List[Trade]): List of Trade objects.
175
+ backtest_run (BacktestRun): The backtest run containing trades.
176
+
177
+ Returns:
178
+ float: The average trade duration in hours.
179
+ """
180
+ if trades is None or len(trades) == 0:
181
+ raise OperationalException(
182
+ "Trades list is None, cannot compute average trade duration."
183
+ )
184
+
185
+ total_duration = 0.0
186
+
187
+ for trade in trades:
188
+
189
+ if TradeStatus.CLOSED.equals(trade.status):
190
+ total_duration += (trade.closed_at - trade.opened_at)\
191
+ .total_seconds() / 3600
192
+ else:
193
+ total_duration += (
194
+ backtest_run.backtest_end_date - trade.opened_at
195
+ ).total_seconds() / 3600
196
+
197
+ number_of_trades = len(trades)
198
+ return total_duration / number_of_trades if number_of_trades > 0 else 0.0
199
+
200
+
201
+ def get_average_trade_size(
202
+ trades: List[Trade]
203
+ ) -> float:
204
+ """
205
+ Calculate the average trade size based on the amount
206
+ and open price of each trade.
207
+
208
+ Args:
209
+ trades (List[Trade]): List of Trade objects.
210
+
211
+ Returns:
212
+ float: The average trade size.
213
+ """
214
+
215
+ if trades is None or len(trades) == 0:
216
+ raise OperationalException(
217
+ "Trades list is None, cannot compute average trade size."
218
+ )
219
+
220
+ total_trade_size = 0.0
221
+
222
+ for trade in trades:
223
+ total_trade_size += trade.amount * trade.open_price
224
+
225
+ number_of_trades = get_number_of_trades(trades)
226
+ return total_trade_size / number_of_trades if number_of_trades > 0 else 0.0
227
+
228
+
229
+ def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]:
230
+ """
231
+ Calculate the average return (absolute PnL) and
232
+ average return percentage (per trade) of closed trades.
233
+ """
234
+ if not trades or len(trades) == 0:
235
+ raise OperationalException(
236
+ "Trades list is empty, cannot compute average return."
237
+ )
238
+
239
+ closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
240
+
241
+ if not closed_trades:
242
+ return 0.0, 0.0
243
+
244
+ total_return = sum(t.net_gain_absolute for t in closed_trades)
245
+ average_return = total_return / len(closed_trades)
246
+
247
+ percentage_returns = [
248
+ (t.net_gain_absolute / t.cost) for t in closed_trades if t.cost > 0
249
+ ]
250
+ average_return_percentage = (
251
+ sum(percentage_returns) / len(percentage_returns)
252
+ if percentage_returns else 0.0
253
+ )
254
+
255
+ return average_return, average_return_percentage
256
+
257
+
258
+ def get_current_average_trade_return(
259
+ trades: List[Trade]
260
+ ) -> Tuple[float, float]:
261
+ """
262
+ Calculate the average return (absolute PnL) and
263
+ average return percentage (per trade) of closed and open trades.
264
+
265
+ Args:
266
+ trades (List[Trade]): List of trades.
267
+
268
+ Returns:
269
+ Tuple[float, float]: The average return
270
+ percentage of the average return
271
+ """
272
+ if not trades or len(trades) == 0:
273
+ raise OperationalException(
274
+ "Trades list is empty, cannot compute average return."
275
+ )
276
+
277
+ total_return = sum(t.net_gain_absolute for t in trades)
278
+ average_return = total_return / len(trades)
279
+
280
+ percentage_returns = [
281
+ (t.net_gain_absolute / t.cost) for t in trades if t.cost > 0
282
+ ]
283
+ average_return_percentage = (
284
+ sum(percentage_returns) / len(percentage_returns)
285
+ if percentage_returns else 0.0
286
+ )
287
+
288
+ return average_return, average_return_percentage
289
+
290
+
291
+ def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
292
+ """
293
+ Calculate the average gain from a list of trades.
294
+
295
+ The average gain is calculated as the mean of all positive returns.
296
+
297
+ Args:
298
+ trades (List[Trade]): List of trades.
299
+
300
+ Returns:
301
+ Tuple[float, float]: The average gain and average gain percentage
302
+ """
303
+ if trades is None or len(trades) == 0:
304
+ raise OperationalException(
305
+ "Trades list is empty or None, cannot calculate average gain."
306
+ )
307
+
308
+ gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
309
+
310
+ if not gains:
311
+ return 0.0, 0.0
312
+
313
+ average_gain = sum(gains) / len(gains)
314
+
315
+ # Updated percentage calculation to match other functions
316
+ percentage_returns = [
317
+ (t.net_gain_absolute / t.cost) for t in trades
318
+ if t.net_gain_absolute > 0 and t.cost > 0
319
+ ]
320
+ average_gain_percentage = (
321
+ sum(percentage_returns) / len(percentage_returns)
322
+ if percentage_returns else 0.0
323
+ )
324
+
325
+ return average_gain, average_gain_percentage
326
+
327
+
328
+ def get_current_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
329
+ """
330
+ Calculate the average gain from a list of trades,
331
+ including both closed and open trades.
332
+
333
+ The average gain is calculated as the mean of all positive returns.
334
+
335
+ Args:
336
+ trades (List[Trade]): List of trades.
337
+ Returns:
338
+ Tuple[float, float]: The average gain and average gain percentage
339
+ """
340
+ if trades is None or len(trades) == 0:
341
+ raise OperationalException(
342
+ "Trades list is empty or None, cannot calculate average gain."
343
+ )
344
+
345
+ gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
346
+
347
+ if not gains:
348
+ return 0.0, 0.0
349
+
350
+ average_gain = sum(gains) / len(gains)
351
+
352
+ # Updated percentage calculation to match other functions
353
+ percentage_returns = [
354
+ (t.net_gain_absolute / t.cost) for t in trades
355
+ if t.net_gain_absolute > 0 and t.cost > 0
356
+ ]
357
+ average_gain_percentage = (
358
+ sum(percentage_returns) / len(percentage_returns)
359
+ if percentage_returns else 0.0
360
+ )
361
+
362
+ return average_gain, average_gain_percentage
363
+
364
+
365
+ def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]:
366
+ """
367
+ Calculate the average loss from a list of trades.
368
+
369
+ The average loss is calculated as the mean of all negative returns.
370
+
371
+ Args:
372
+ trades (List[Trade]): List of trades.
373
+ Returns:
374
+ Tuple[float, float]: The average loss
375
+ percentage of the average loss
376
+ """
377
+ if trades is None or len(trades) == 0:
378
+ raise OperationalException(
379
+ "Trades list is empty or None, cannot calculate average loss."
380
+ )
381
+
382
+ closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
383
+ losing_trades = [t for t in closed_trades if t.net_gain < 0]
384
+
385
+ if not losing_trades or len(losing_trades) == 0:
386
+ return 0.0, 0.0
387
+
388
+ losses = [t.net_gain_absolute for t in losing_trades]
389
+ average_loss = sum(losses) / len(losses)
390
+ percentage_returns = [
391
+ (t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
392
+ ]
393
+ average_return_percentage = (
394
+ sum(percentage_returns) / len(percentage_returns)
395
+ if percentage_returns else 0.0
396
+ )
397
+ return average_loss, average_return_percentage
398
+
399
+
400
+ def get_current_average_trade_loss(
401
+ trades: List[Trade]
402
+ ) -> Tuple[float, float]:
403
+ """
404
+ Calculate the average loss from a list of trades,
405
+ including both closed and open trades.
406
+
407
+ The average loss is calculated as the mean of all negative returns.
408
+
409
+ Args:
410
+ trades (List[Trade]): List of trades.
411
+
412
+ Returns:
413
+ Tuple[float, float]: The average loss
414
+ percentage of the average loss
415
+ """
416
+ if trades is None or len(trades) == 0:
417
+ raise OperationalException(
418
+ "Trades list is empty or None, cannot calculate average loss."
419
+ )
420
+
421
+ losing_trades = [t for t in trades if t.net_gain_absolute < 0]
422
+
423
+ if not losing_trades or len(losing_trades) == 0:
424
+ return 0.0, 0.0
425
+
426
+ losses = [t.net_gain_absolute for t in losing_trades]
427
+ average_loss = sum(losses) / len(losses)
428
+ percentage_returns = [
429
+ (t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
430
+ ]
431
+ average_return_percentage = (
432
+ sum(percentage_returns) / len(percentage_returns)
433
+ if percentage_returns else 0.0
434
+ )
435
+ return average_loss, average_return_percentage
436
+
437
+
438
+ def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]:
439
+ """
440
+ Calculate the median return from a list of trades.
441
+
442
+ The median return is calculated as the median of all returns.
443
+
444
+ Args:
445
+ trades (List[Trade]): List of trades.
446
+
447
+ Returns:
448
+ Tuple[float, float]: The median return
449
+ percentage of the median return
450
+ """
451
+
452
+ if not trades:
453
+ return 0.0, 0.0
454
+
455
+ sorted_returns = sorted(t.net_gain_absolute for t in trades)
456
+ n = len(sorted_returns)
457
+ mid = n // 2
458
+
459
+ if n % 2 == 0:
460
+ median_return = (sorted_returns[mid - 1] + sorted_returns[mid]) / 2
461
+ else:
462
+ median_return = sorted_returns[mid]
463
+
464
+ cost = sum(t.cost for t in trades)
465
+ percentage = (median_return / cost) if cost > 0 else 0.0
466
+ return median_return, percentage
467
+
468
+
469
+ def get_best_trade(trades: List[Trade]) -> Trade:
470
+ """
471
+ Get the trade with the highest net gain.
472
+
473
+ Args:
474
+ trades (List[Trade]): List of trades.
475
+
476
+ Returns:
477
+ Trade: The trade with the highest net gain.
478
+ """
479
+
480
+ if not trades:
481
+ return None
482
+
483
+ return max(trades, key=lambda t: t.net_gain_absolute)
484
+
485
+
486
+ def get_worst_trade(trades: List[Trade]) -> Trade:
487
+ """
488
+ Get the trade with the lowest net gain (worst trade).
489
+
490
+ Args:
491
+ trades (List[Trade]): List of trades.
492
+
493
+ Returns:
494
+ Trade: The trade with the lowest net gain.
495
+ """
496
+
497
+ if not trades:
498
+ return None
499
+
500
+ return min(trades, key=lambda t: t.net_gain)
@@ -0,0 +1,97 @@
1
+ """
2
+ Volatility is a statistical measure of the dispersion of returns for a
3
+ given portfolio. In finance, it is commonly used as a proxy for risk.
4
+ This function calculates the standard deviation of daily log returns and
5
+ annualizes it, giving an estimate of how much the portfolio's value
6
+ fluctuates on a yearly basis.
7
+
8
+ | **Annual Volatility** | **Risk Level (Standalone)** | **Context Matters: Sharpe Ratio Impact** | **Comments** |
9
+ | --------------------- | --------------------------- | ---------------------------------------- | ----------- |
10
+ | **< 5%** | Very Low Risk | Sharpe > 2.0 = Excellent<br>Sharpe < 0.5 = Poor | Low volatility is great unless returns are negative |
11
+ | **5% – 10%** | Low Risk | Sharpe > 1.0 = Good<br>Sharpe < 0.3 = Mediocre | Typical for conservative portfolios |
12
+ | **10% – 15%** | Moderate Risk | Sharpe > 0.8 = Good<br>Sharpe < 0.2 = Risky | S&P 500 benchmark; quality matters |
13
+ | **15% – 25%** | High Risk | Sharpe > 0.6 = Acceptable<br>Sharpe < 0.0 = Avoid | **Example: 30% CAGR + 23% vol = Sharpe ~1.3 = Excellent** |
14
+ | **> 25%** | Very High Risk | Sharpe > 0.4 = Maybe acceptable<br>Sharpe < 0.0 = Dangerous | Only viable with strong positive returns |
15
+
16
+
17
+ Key takeaway: Don't interpret volatility in isolation. Always calculate
18
+ and compare the Sharpe Ratio to assess true strategy quality.
19
+ Your 30% CAGR with 23% volatility is exceptional because the return far outweighs the risk taken.
20
+
21
+ """
22
+
23
+ from typing import List
24
+
25
+ import pandas as pd
26
+ import numpy as np
27
+
28
+ from investing_algorithm_framework.domain import PortfolioSnapshot
29
+
30
+
31
+ def get_annual_volatility(
32
+ snapshots: List[PortfolioSnapshot],
33
+ trading_days_per_year=365
34
+ ) -> float:
35
+ """
36
+ Calculate the annualized volatility of portfolio net values.
37
+
38
+ !Important Note:
39
+
40
+ Volatility measures variability, not direction. For example:
41
+
42
+ A standard deviation of 0.238 (23.8%) means returns swing
43
+ wildly around their average, but it doesn't tell you if that average
44
+ is positive or negative.
45
+
46
+ Two scenarios with the same 23.8% volatility:
47
+ Mean return = +15% per year, Std = 23.8%
48
+ 16% chance of losing >8.8% (15% - 23.8%)
49
+ 16% chance of gaining >38.8% (15% + 23.8%)
50
+ This is excellent — high growth with swings
51
+
52
+ Mean return = -5% per year, Std = 23.8%
53
+ 16% chance of losing >28.8% (-5% - 23.8%)
54
+ 16% chance of gaining >18.8% (-5% + 23.8%)
55
+ This is terrible — losing money with high risk
56
+
57
+ To assess if "always good returns with high std" is perfect, you need
58
+ to consider risk-adjusted metrics like the Sharpe Ratio:
59
+ Sharpe Ratio = (Mean Return - Risk-Free Rate) / Volatility
60
+ Higher is better; tells you return per unit of risk taken
61
+
62
+ Args:
63
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
64
+ from the backtest report.
65
+ trading_days_per_year (int): Number of trading days in a year.
66
+
67
+ Returns:
68
+ Float: Annualized volatility as a float
69
+ """
70
+
71
+ if len(snapshots) < 2:
72
+ return 0.0
73
+
74
+ # Build DataFrame from snapshots
75
+ records = [
76
+ (snapshot.total_value, snapshot.created_at) for snapshot in snapshots
77
+ ]
78
+ df = pd.DataFrame(records, columns=['total_value', 'created_at'])
79
+ df['created_at'] = pd.to_datetime(df['created_at'])
80
+ df = df.set_index('created_at').sort_index().drop_duplicates()
81
+
82
+ # Resample to daily frequency, taking the last value of each day
83
+ df_daily = df.resample('D').last()
84
+ df_daily = df_daily.dropna()
85
+
86
+ if len(df_daily) < 2:
87
+ return 0.0
88
+
89
+ # Calculate log returns on daily data
90
+ df_daily['log_return'] = np.log(df_daily['total_value'] / df_daily['total_value'].shift(1))
91
+ df_daily = df_daily.dropna()
92
+
93
+ # Calculate daily volatility (standard deviation of daily returns)
94
+ daily_volatility = df_daily['log_return'].std()
95
+
96
+ # Annualize using trading days per year
97
+ return daily_volatility * np.sqrt(trading_days_per_year)