investing-algorithm-framework 1.3.1__py3-none-any.whl → 7.25.6__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.
Files changed (282) hide show
  1. investing_algorithm_framework/__init__.py +195 -16
  2. investing_algorithm_framework/analysis/__init__.py +16 -0
  3. investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
  4. investing_algorithm_framework/analysis/data.py +170 -0
  5. investing_algorithm_framework/analysis/markdown.py +91 -0
  6. investing_algorithm_framework/analysis/ranking.py +298 -0
  7. investing_algorithm_framework/app/__init__.py +31 -4
  8. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  9. investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
  10. investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
  11. investing_algorithm_framework/app/app.py +2233 -264
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1724 -0
  14. investing_algorithm_framework/app/eventloop.py +620 -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 +6 -3
  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 +2 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
  41. investing_algorithm_framework/app/strategy.py +873 -52
  42. investing_algorithm_framework/app/task.py +5 -3
  43. investing_algorithm_framework/app/web/__init__.py +2 -1
  44. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  45. investing_algorithm_framework/app/web/controllers/orders.py +4 -3
  46. investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
  47. investing_algorithm_framework/app/web/controllers/positions.py +3 -3
  48. investing_algorithm_framework/app/web/create_app.py +4 -2
  49. investing_algorithm_framework/app/web/error_handler.py +1 -1
  50. investing_algorithm_framework/app/web/schemas/order.py +2 -2
  51. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  52. investing_algorithm_framework/cli/__init__.py +0 -0
  53. investing_algorithm_framework/cli/cli.py +231 -0
  54. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  55. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  56. investing_algorithm_framework/cli/initialize_app.py +603 -0
  57. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  58. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  59. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  60. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  61. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  62. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  63. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  64. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  65. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  66. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  67. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  68. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  69. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  70. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  71. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  72. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  73. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  74. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  75. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  76. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  77. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  78. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  79. investing_algorithm_framework/create_app.py +43 -9
  80. investing_algorithm_framework/dependency_container.py +121 -33
  81. investing_algorithm_framework/domain/__init__.py +109 -22
  82. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  83. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  84. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  87. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  88. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  92. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  93. investing_algorithm_framework/domain/config.py +60 -138
  94. investing_algorithm_framework/domain/constants.py +23 -34
  95. investing_algorithm_framework/domain/data_provider.py +334 -0
  96. investing_algorithm_framework/domain/data_structures.py +42 -0
  97. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  98. investing_algorithm_framework/domain/exceptions.py +51 -1
  99. investing_algorithm_framework/domain/models/__init__.py +29 -14
  100. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  101. investing_algorithm_framework/domain/models/base_model.py +3 -1
  102. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  104. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  105. investing_algorithm_framework/domain/models/event.py +35 -0
  106. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  107. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  108. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  109. investing_algorithm_framework/domain/models/order/order.py +243 -86
  110. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  111. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  112. investing_algorithm_framework/domain/models/portfolio/__init__.py +7 -2
  113. investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
  114. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
  115. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  116. investing_algorithm_framework/domain/models/position/__init__.py +3 -2
  117. investing_algorithm_framework/domain/models/position/position.py +29 -0
  118. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  119. investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
  120. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  121. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  122. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -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 +94 -98
  126. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +111 -2
  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 +11 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  132. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  133. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  134. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  135. investing_algorithm_framework/domain/order_executor.py +112 -0
  136. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  137. investing_algorithm_framework/domain/services/__init__.py +11 -0
  138. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  139. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  140. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  141. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  142. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  143. investing_algorithm_framework/domain/strategy.py +1 -29
  144. investing_algorithm_framework/domain/utils/__init__.py +16 -4
  145. investing_algorithm_framework/domain/utils/csv.py +22 -0
  146. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  147. investing_algorithm_framework/domain/utils/dates.py +57 -0
  148. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  149. investing_algorithm_framework/domain/utils/polars.py +53 -0
  150. investing_algorithm_framework/domain/utils/random.py +29 -0
  151. investing_algorithm_framework/download_data.py +244 -0
  152. investing_algorithm_framework/infrastructure/__init__.py +39 -11
  153. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  154. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  155. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  156. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  157. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  158. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +87 -13
  159. investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
  160. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  161. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  162. investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
  163. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  164. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  165. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +3 -2
  166. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  167. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
  168. investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
  169. investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
  170. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  171. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  172. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  173. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  174. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  175. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  176. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  177. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  178. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  179. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  180. investing_algorithm_framework/infrastructure/repositories/__init__.py +13 -5
  181. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  182. investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
  183. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  184. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  185. investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
  186. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  187. investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
  188. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  189. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  190. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  191. investing_algorithm_framework/infrastructure/services/__init__.py +9 -2
  192. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  193. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  194. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  195. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  196. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  197. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  198. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  199. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  200. investing_algorithm_framework/services/__init__.py +127 -10
  201. investing_algorithm_framework/services/configuration_service.py +95 -0
  202. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  203. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  204. investing_algorithm_framework/services/market_credential_service.py +40 -0
  205. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  206. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  207. investing_algorithm_framework/services/metrics/beta.py +0 -0
  208. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  209. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  210. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  211. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  212. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  213. investing_algorithm_framework/services/metrics/generate.py +358 -0
  214. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  215. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  216. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  217. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  218. investing_algorithm_framework/services/metrics/returns.py +452 -0
  219. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  220. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  221. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  222. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  223. investing_algorithm_framework/services/metrics/trades.py +473 -0
  224. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  225. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  226. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  227. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  228. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  229. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  230. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  231. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  232. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  233. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  234. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  235. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  236. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  237. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  238. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  239. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  240. investing_algorithm_framework/services/positions/__init__.py +7 -0
  241. investing_algorithm_framework/services/positions/position_service.py +210 -0
  242. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  243. investing_algorithm_framework/services/repository_service.py +8 -2
  244. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  245. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  246. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  247. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  248. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  249. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  250. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  251. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  252. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  253. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  254. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  255. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  256. investing_algorithm_framework/app/algorithm.py +0 -410
  257. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  258. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  259. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
  260. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  261. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  262. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  263. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  264. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
  265. investing_algorithm_framework/domain/singleton.py +0 -9
  266. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  267. investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
  268. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  269. investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
  270. investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
  271. investing_algorithm_framework/services/market_data_service.py +0 -75
  272. investing_algorithm_framework/services/order_service.py +0 -464
  273. investing_algorithm_framework/services/portfolio_service.py +0 -105
  274. investing_algorithm_framework/services/position_cost_service.py +0 -5
  275. investing_algorithm_framework/services/position_service.py +0 -50
  276. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
  277. investing_algorithm_framework/setup_logging.py +0 -40
  278. investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
  279. investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
  280. investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
  281. investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
  282. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,156 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ def get_standard_deviation_downside_returns(snapshots):
6
+ """
7
+ Calculate the standard deviation of downside returns from the net size
8
+ of the reports.
9
+
10
+ Args:
11
+ report (BacktestReport): The report containing the equity curve.
12
+
13
+ Returns:
14
+ float: The standard deviation of downside returns.
15
+ """
16
+
17
+ if len(snapshots) < 2:
18
+ return 0.0 # Not enough data
19
+
20
+ # Create DataFrame of net_size over time
21
+ data = [(s.total_value, s.created_at) for s in snapshots]
22
+ df = pd.DataFrame(data, columns=["total_value", "created_at"])
23
+ df['created_at'] = pd.to_datetime(df['created_at'])
24
+ df = df.sort_values('created_at').drop_duplicates('created_at').copy()
25
+
26
+ # Compute percentage returns
27
+ df['return'] = df['total_value'].pct_change()
28
+ df = df.dropna()
29
+
30
+ if df.empty:
31
+ return 0.0
32
+
33
+ # Filter downside returns
34
+ downside_returns = df['return'][df['return'] < 0]
35
+
36
+ if downside_returns.empty:
37
+ return 0.0
38
+
39
+ # Compute standard deviation of downside returns
40
+ downside_std = downside_returns.std(ddof=1) # ddof=1 for sample std dev
41
+
42
+ # Handle edge cases
43
+ if np.isnan(downside_std):
44
+ return 0.0
45
+
46
+ return downside_std
47
+
48
+
49
+ def get_standard_deviation_returns(snapshots):
50
+ """
51
+ Calculate the standard deviation of returns from the net size
52
+ of the reports.
53
+
54
+ Args:
55
+ report (BacktestReport): The report containing the equity curve.
56
+
57
+ Returns:
58
+ float: The standard deviation of downside returns.
59
+ """
60
+
61
+ if len(snapshots) < 2:
62
+ return 0.0 # Not enough data
63
+
64
+ # Create DataFrame of net_size over time
65
+ data = [(s.total_value, s.created_at) for s in snapshots]
66
+ df = pd.DataFrame(data, columns=["total_value", "created_at"])
67
+ df['created_at'] = pd.to_datetime(df['created_at'])
68
+ df = df.sort_values('created_at').drop_duplicates('created_at').copy()
69
+
70
+ # Compute percentage returns
71
+ df['return'] = df['total_value'].pct_change()
72
+ df = df.dropna()
73
+
74
+ if df.empty:
75
+ return 0.0
76
+
77
+ # Filter downside returns
78
+ df_returns = df['return']
79
+
80
+ if df_returns.empty:
81
+ return 0.0
82
+
83
+ std = df_returns.std(ddof=1) # ddof=1 for sample std dev
84
+
85
+ # Handle edge cases
86
+ if np.isnan(std):
87
+ return 0.0
88
+
89
+ return std
90
+
91
+ def get_daily_returns_std(snapshots):
92
+ """
93
+ Calculate the standard deviation of daily returns from a list of snapshots.
94
+ Resamples data to daily frequency using end-of-day values.
95
+
96
+ Args:
97
+ snapshots (List[PortfolioSnapshot]): Snapshots with total_value and created_at.
98
+
99
+ Returns:
100
+ float: Standard deviation of daily returns.
101
+ """
102
+ if len(snapshots) < 2:
103
+ return 0.0 # Not enough data
104
+
105
+ # Create DataFrame from snapshots
106
+ data = [(s.created_at, s.total_value) for s in snapshots]
107
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
108
+ df["created_at"] = pd.to_datetime(df["created_at"])
109
+ df = df.drop_duplicates("created_at").set_index("created_at")
110
+ df = df.sort_index()
111
+ # Resample to daily frequency (end of day)
112
+ daily_df = df.resample("D").last().ffill().dropna()
113
+
114
+ # Calculate daily returns
115
+ daily_df["return"] = daily_df["total_value"].pct_change().dropna()
116
+
117
+ if daily_df["return"].empty:
118
+ return 0.0
119
+
120
+ return daily_df["return"].std()
121
+
122
+
123
+ def get_downside_std_of_daily_returns(snapshots):
124
+ """
125
+ Calculate the downside standard deviation of daily returns from a list of snapshots.
126
+ Resamples data to daily frequency using end-of-day values.
127
+
128
+ Args:
129
+ snapshots (List[PortfolioSnapshot]): Snapshots with total_value and created_at.
130
+
131
+ Returns:
132
+ float: Downside standard deviation of daily returns.
133
+ """
134
+ if len(snapshots) < 2:
135
+ return 0.0 # Not enough data
136
+
137
+ # Create DataFrame from snapshots
138
+ data = [(s.created_at, s.total_value) for s in snapshots]
139
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
140
+ df["created_at"] = pd.to_datetime(df["created_at"])
141
+ df = df.drop_duplicates("created_at").set_index("created_at")
142
+ df = df.sort_index()
143
+
144
+ # Resample to daily frequency (end of day)
145
+ daily_df = df.resample("D").last().dropna()
146
+
147
+ # Calculate daily returns
148
+ daily_df["return"] = daily_df["total_value"].pct_change().dropna()
149
+
150
+ # Filter only negative returns for downside deviation
151
+ negative_returns = daily_df["return"][daily_df["return"] < 0]
152
+
153
+ if negative_returns.empty:
154
+ return 0.0
155
+
156
+ return negative_returns.std()
@@ -0,0 +1,473 @@
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
+ return 0, 0.0
22
+
23
+ closed_trades = [
24
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
25
+ ]
26
+
27
+ positive_trades = [
28
+ trade for trade in closed_trades if trade.net_gain_absolute > 0
29
+ ]
30
+ number_of_positive_trades = len(positive_trades)
31
+ percentage_positive_trades = (
32
+ (number_of_positive_trades / len(closed_trades)) * 100.0
33
+ if len(closed_trades) > 0 else 0.0
34
+ )
35
+ return number_of_positive_trades, percentage_positive_trades
36
+
37
+
38
+ def get_negative_trades(
39
+ trades: List[Trade]
40
+ ) -> Tuple[int, float]:
41
+ """
42
+ Calculate the number and percentage of negative trades.
43
+
44
+ Args:
45
+ trades (List[Trade]): List of Trade objects.
46
+
47
+ Returns:
48
+ Tuple[int, float]: A tuple containing the number of negative trades
49
+ and the percentage ofr negative trades.
50
+ """
51
+ if trades is None or len(trades) == 0:
52
+ return 0, 0.0
53
+
54
+ closed_trades = [
55
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
56
+ ]
57
+
58
+ negative_trades = [
59
+ trade for trade in closed_trades if trade.net_gain_absolute < 0
60
+ ]
61
+ number_of_negative_trades = len(negative_trades)
62
+ percentage_negative_trades = (
63
+ (number_of_negative_trades / len(closed_trades)) * 100.0
64
+ if len(closed_trades) > 0 else 0.0
65
+ )
66
+ return number_of_negative_trades, percentage_negative_trades
67
+
68
+
69
+ def get_number_of_trades(
70
+ trades: List[Trade]
71
+ ) -> int:
72
+ """
73
+ Calculate the total number of trades.
74
+
75
+ Args:
76
+ trades (List[Trade]): List of Trade objects.
77
+
78
+ Returns:
79
+ int: The total number of trades.
80
+ """
81
+ if trades is None:
82
+ return 0
83
+
84
+ return len(trades)
85
+
86
+
87
+ def get_number_of_open_trades(
88
+ trades: List[Trade]
89
+ ) -> int:
90
+ """
91
+ Calculate the number of open trades.
92
+
93
+ Args:
94
+ trades (List[Trade]): List of Trade objects.
95
+
96
+ Returns:
97
+ int: The number of open trades.
98
+ """
99
+ if trades is None:
100
+ return 0
101
+
102
+ open_trades = [
103
+ trade for trade in trades if TradeStatus.OPEN.equals(trade.status)
104
+ ]
105
+ return len(open_trades)
106
+
107
+
108
+ def get_number_of_closed_trades(
109
+ trades: List[Trade]
110
+ ) -> int:
111
+ """
112
+ Calculate the number of closed trades.
113
+
114
+ Args:
115
+ trades (List[Trade]): List of Trade objects.
116
+
117
+ Returns:
118
+ int: The number of closed trades.
119
+ """
120
+ if trades is None:
121
+ return 0
122
+
123
+ closed_trades = [
124
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
125
+ ]
126
+ return len(closed_trades)
127
+
128
+
129
+ def get_average_trade_duration(
130
+ trades: List[Trade]
131
+ ) -> float:
132
+ """
133
+ Calculate the average duration of closed trades in hours.
134
+
135
+ Args:
136
+ trades (List[Trade]): List of Trade objects.
137
+
138
+ Returns:
139
+ float: The average trade duration in hours.
140
+ """
141
+ if trades is None:
142
+ return 0.0
143
+
144
+ total_duration = 0.0
145
+
146
+ for trade in trades:
147
+ if TradeStatus.CLOSED.equals(trade.status):
148
+ total_duration += (trade.closed_at - trade.opened_at)\
149
+ .total_seconds() / 3600
150
+
151
+ number_of_trades = get_number_of_closed_trades(trades)
152
+ return total_duration / number_of_trades if number_of_trades > 0 else 0.0
153
+
154
+
155
+ def get_current_average_trade_duration(
156
+ trades: List[Trade], backtest_run: BacktestRun
157
+ ) -> float:
158
+ """
159
+ Calculate the average duration of currently closed and open trades
160
+ in hours.
161
+
162
+ Args:
163
+ trades (List[Trade]): List of Trade objects.
164
+ backtest_run (BacktestRun): The backtest run containing trades.
165
+
166
+ Returns:
167
+ float: The average trade duration in hours.
168
+ """
169
+ if trades is None:
170
+ return 0.0
171
+
172
+ total_duration = 0.0
173
+
174
+ for trade in trades:
175
+
176
+ if TradeStatus.CLOSED.equals(trade.status):
177
+ total_duration += (trade.closed_at - trade.opened_at)\
178
+ .total_seconds() / 3600
179
+ else:
180
+ total_duration += (
181
+ backtest_run.backtest_end_date - trade.opened_at
182
+ ).total_seconds() / 3600
183
+
184
+ number_of_trades = len(trades)
185
+ return total_duration / number_of_trades if number_of_trades > 0 else 0.0
186
+
187
+
188
+ def get_average_trade_size(
189
+ trades: List[Trade]
190
+ ) -> float:
191
+ """
192
+ Calculate the average trade size based on the amount
193
+ and open price of each trade.
194
+
195
+ Args:
196
+ trades (List[Trade]): List of Trade objects.
197
+
198
+ Returns:
199
+ float: The average trade size.
200
+ """
201
+
202
+ if trades is None:
203
+ return 0.0
204
+
205
+ total_trade_size = 0.0
206
+
207
+ for trade in trades:
208
+ total_trade_size += trade.amount * trade.open_price
209
+
210
+ number_of_trades = get_number_of_trades(trades)
211
+ return total_trade_size / number_of_trades if number_of_trades > 0 else 0.0
212
+
213
+
214
+ def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]:
215
+ """
216
+ Calculate the average return (absolute PnL) and
217
+ average return percentage (per trade) of closed trades.
218
+ """
219
+ if not trades or len(trades) == 0:
220
+ return 0.0, 0.0
221
+
222
+ closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
223
+
224
+ if not closed_trades:
225
+ return 0.0, 0.0
226
+
227
+ total_return = sum(t.net_gain_absolute for t in closed_trades)
228
+ average_return = total_return / len(closed_trades)
229
+
230
+ percentage_returns = [
231
+ (t.net_gain_absolute / t.cost) for t in closed_trades if t.cost > 0
232
+ ]
233
+ average_return_percentage = (
234
+ sum(percentage_returns) / len(percentage_returns)
235
+ if percentage_returns else 0.0
236
+ )
237
+
238
+ return average_return, average_return_percentage
239
+
240
+
241
+ def get_current_average_trade_return(
242
+ trades: List[Trade]
243
+ ) -> Tuple[float, float]:
244
+ """
245
+ Calculate the average return (absolute PnL) and
246
+ average return percentage (per trade) of closed and open trades.
247
+
248
+ Args:
249
+ trades (List[Trade]): List of trades.
250
+
251
+ Returns:
252
+ Tuple[float, float]: The average return
253
+ percentage of the average return
254
+ """
255
+ if not trades or len(trades) == 0:
256
+ return 0.0, 0.0
257
+
258
+ total_return = sum(t.net_gain_absolute for t in trades)
259
+ average_return = total_return / len(trades)
260
+
261
+ percentage_returns = [
262
+ (t.net_gain_absolute / t.cost) for t in trades if t.cost > 0
263
+ ]
264
+ average_return_percentage = (
265
+ sum(percentage_returns) / len(percentage_returns)
266
+ if percentage_returns else 0.0
267
+ )
268
+
269
+ return average_return, average_return_percentage
270
+
271
+
272
+ def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
273
+ """
274
+ Calculate the average gain from a list of trades.
275
+
276
+ The average gain is calculated as the mean of all positive returns.
277
+
278
+ Args:
279
+ trades (List[Trade]): List of trades.
280
+
281
+ Returns:
282
+ Tuple[float, float]: The average gain and average gain percentage
283
+ """
284
+ if trades is None or len(trades) == 0:
285
+ return 0.0, 0.0
286
+
287
+ gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
288
+
289
+ if not gains:
290
+ return 0.0, 0.0
291
+
292
+ average_gain = sum(gains) / len(gains)
293
+
294
+ # Updated percentage calculation to match other functions
295
+ percentage_returns = [
296
+ (t.net_gain_absolute / t.cost) for t in trades
297
+ if t.net_gain_absolute > 0 and t.cost > 0
298
+ ]
299
+ average_gain_percentage = (
300
+ sum(percentage_returns) / len(percentage_returns)
301
+ if percentage_returns else 0.0
302
+ )
303
+
304
+ return average_gain, average_gain_percentage
305
+
306
+
307
+ def get_current_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
308
+ """
309
+ Calculate the average gain from a list of trades,
310
+ including both closed and open trades.
311
+
312
+ The average gain is calculated as the mean of all positive returns.
313
+
314
+ Args:
315
+ trades (List[Trade]): List of trades.
316
+ Returns:
317
+ Tuple[float, float]: The average gain and average gain percentage
318
+ """
319
+ if trades is None or len(trades) == 0:
320
+ return 0.0, 0.0
321
+
322
+ gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
323
+
324
+ if not gains:
325
+ return 0.0, 0.0
326
+
327
+ average_gain = sum(gains) / len(gains)
328
+
329
+ # Updated percentage calculation to match other functions
330
+ percentage_returns = [
331
+ (t.net_gain_absolute / t.cost) for t in trades
332
+ if t.net_gain_absolute > 0 and t.cost > 0
333
+ ]
334
+ average_gain_percentage = (
335
+ sum(percentage_returns) / len(percentage_returns)
336
+ if percentage_returns else 0.0
337
+ )
338
+
339
+ return average_gain, average_gain_percentage
340
+
341
+
342
+ def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]:
343
+ """
344
+ Calculate the average loss from a list of trades.
345
+
346
+ The average loss is calculated as the mean of all negative returns.
347
+
348
+ Args:
349
+ trades (List[Trade]): List of trades.
350
+ Returns:
351
+ Tuple[float, float]: The average loss
352
+ percentage of the average loss
353
+ """
354
+ if trades is None or len(trades) == 0:
355
+ return 0.0, 0.0
356
+
357
+ closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
358
+ losing_trades = [t for t in closed_trades if t.net_gain < 0]
359
+
360
+ if not losing_trades or len(losing_trades) == 0:
361
+ return 0.0, 0.0
362
+
363
+ losses = [t.net_gain_absolute for t in losing_trades]
364
+ average_loss = sum(losses) / len(losses)
365
+ percentage_returns = [
366
+ (t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
367
+ ]
368
+ average_return_percentage = (
369
+ sum(percentage_returns) / len(percentage_returns)
370
+ if percentage_returns else 0.0
371
+ )
372
+ return average_loss, average_return_percentage
373
+
374
+
375
+ def get_current_average_trade_loss(
376
+ trades: List[Trade]
377
+ ) -> Tuple[float, float]:
378
+ """
379
+ Calculate the average loss from a list of trades,
380
+ including both closed and open trades.
381
+
382
+ The average loss is calculated as the mean of all negative returns.
383
+
384
+ Args:
385
+ trades (List[Trade]): List of trades.
386
+
387
+ Returns:
388
+ Tuple[float, float]: The average loss
389
+ percentage of the average loss
390
+ """
391
+ if trades is None or len(trades) == 0:
392
+ return 0.0, 0.0
393
+
394
+ losing_trades = [t for t in trades if t.net_gain_absolute < 0]
395
+
396
+ if not losing_trades or len(losing_trades) == 0:
397
+ return 0.0, 0.0
398
+
399
+ losses = [t.net_gain_absolute for t in losing_trades]
400
+ average_loss = sum(losses) / len(losses)
401
+ percentage_returns = [
402
+ (t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
403
+ ]
404
+ average_return_percentage = (
405
+ sum(percentage_returns) / len(percentage_returns)
406
+ if percentage_returns else 0.0
407
+ )
408
+ return average_loss, average_return_percentage
409
+
410
+
411
+ def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]:
412
+ """
413
+ Calculate the median return from a list of trades.
414
+
415
+ The median return is calculated as the median of all returns.
416
+
417
+ Args:
418
+ trades (List[Trade]): List of trades.
419
+
420
+ Returns:
421
+ Tuple[float, float]: The median return
422
+ percentage of the median return
423
+ """
424
+
425
+ if not trades:
426
+ return 0.0, 0.0
427
+
428
+ sorted_returns = sorted(t.net_gain_absolute for t in trades)
429
+ n = len(sorted_returns)
430
+ mid = n // 2
431
+
432
+ if n % 2 == 0:
433
+ median_return = (sorted_returns[mid - 1] + sorted_returns[mid]) / 2
434
+ else:
435
+ median_return = sorted_returns[mid]
436
+
437
+ cost = sum(t.cost for t in trades)
438
+ percentage = (median_return / cost) if cost > 0 else 0.0
439
+ return median_return, percentage
440
+
441
+
442
+ def get_best_trade(trades: List[Trade]) -> Trade:
443
+ """
444
+ Get the trade with the highest net gain.
445
+
446
+ Args:
447
+ trades (List[Trade]): List of trades.
448
+
449
+ Returns:
450
+ Trade: The trade with the highest net gain.
451
+ """
452
+
453
+ if not trades:
454
+ return None
455
+
456
+ return max(trades, key=lambda t: t.net_gain_absolute)
457
+
458
+
459
+ def get_worst_trade(trades: List[Trade]) -> Trade:
460
+ """
461
+ Get the trade with the lowest net gain (worst trade).
462
+
463
+ Args:
464
+ trades (List[Trade]): List of trades.
465
+
466
+ Returns:
467
+ Trade: The trade with the lowest net gain.
468
+ """
469
+
470
+ if not trades:
471
+ return None
472
+
473
+ return min(trades, key=lambda t: t.net_gain)