investing-algorithm-framework 7.19.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,137 @@
1
+ """
2
+ The Sharpe Ratio is a widely used risk-adjusted performance metric. It
3
+ measures the excess return per unit of risk (volatility), where risk is
4
+ represented by the standard deviation of returns.
5
+
6
+ | Sharpe Ratio | Interpretation |
7
+ | -------------- | ------------------------------------------- |
8
+ | **< 0** | Bad: Underperforms risk-free asset |
9
+ | **0.0 – 1.0** | Suboptimal: Returns do not justify risk |
10
+ | **1.0 – 1.99** | Acceptable: Reasonable risk-adjusted return |
11
+ | **2.0 – 2.99** | Good: Strong risk-adjusted performance |
12
+ | **3.0+** | Excellent: Exceptional risk-adjusted return |
13
+
14
+ Sharpe Ratio is highly sensitive to the volatility estimate: Inconsistent sampling frequency, short backtests, or low trade frequency can distort it.
15
+
16
+ Different strategies have different risk profiles:
17
+
18
+ High-frequency strategies may have high Sharpe Ratios (>3).
19
+
20
+ Trend-following strategies might have lower Sharpe (1–2) but strong CAGR and Calmar.
21
+
22
+ Use risk-free rate (~4–5% annual currently) if your backtest spans long periods.
23
+
24
+ ### 📌 Practical Notes about the implementation:
25
+
26
+ - Use **daily returns** for consistent Sharpe Ratio calculation and **annualize** the result using this formula:
27
+
28
+
29
+ Sharpe Ratio Formula:
30
+ Sharpe Ratio = (Mean Daily Return × Periods Per Year - Risk-Free Rate) /
31
+ (Standard Deviation of Daily Returns × sqrt(Periods Per Year))
32
+
33
+ - You can also calculate a **rolling Sharpe Ratio** (e.g., over a 90-day window) to detect changes in performance stability over time.
34
+
35
+ Mean daily return is either based on the real returns from the backtest or the CAGR, depending on the data duration.
36
+
37
+ When do we use actual returns vs CAGR?
38
+
39
+ | Data Duration | Use This Approach | Reason |
40
+ | ------------- | --------------------------------------------------------------- | ----------------------------------------------------------------- |
41
+ | **< 1 year** | Use **CAGR** directly and avoid Sharpe Ratio | Not enough data to estimate volatility robustly |
42
+ | **1–2 years** | Use **CAGR + conservative vol estimate** OR Sharpe with caution | Sharpe may be unstable, consider adding error bars or disclaimers |
43
+ | **> 2 years** | Use **Sharpe Ratio** based on periodic returns | Adequate data to reliably estimate risk-adjusted return |
44
+
45
+ """
46
+
47
+ from typing import Optional, List, Tuple
48
+
49
+ import math
50
+ import pandas as pd
51
+ import numpy as np
52
+ from datetime import datetime
53
+
54
+ from investing_algorithm_framework.domain import PortfolioSnapshot
55
+ from .mean_daily_return import get_mean_daily_return
56
+ from .standard_deviation import get_daily_returns_std
57
+
58
+
59
+ def get_sharpe_ratio(
60
+ snapshots: List[PortfolioSnapshot], risk_free_rate: float,
61
+ ) -> float:
62
+ """
63
+ Calculate the Sharpe Ratio from a backtest report using daily or
64
+ weekly returns.
65
+
66
+ The Sharpe Ratio is calculated as:
67
+ (Annualized Return - Risk-Free Rate) / Annualized Std Dev of Returns
68
+
69
+ Args:
70
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
71
+ risk_free_rate (float, optional): Annual risk-free rate as a
72
+ decimal (e.g., 0.047 for 4.7%).
73
+
74
+ Returns:
75
+ float: The Sharpe Ratio.
76
+ """
77
+ snapshots = sorted(snapshots, key=lambda s: s.created_at)
78
+ mean_daily_return = get_mean_daily_return(snapshots)
79
+ std_daily_return = get_daily_returns_std(snapshots)
80
+
81
+ if std_daily_return == 0:
82
+ return float('nan') # Avoid division by zero
83
+
84
+ # Formula: Sharpe Ratio = (Mean Daily Return × Periods Per Year - Risk-Free Rate) /
85
+ # (Standard Deviation of Daily Returns × sqrt(Periods Per Year))
86
+ return (mean_daily_return * 365 - risk_free_rate) / \
87
+ (std_daily_return * math.sqrt(365))
88
+
89
+
90
+ def get_rolling_sharpe_ratio(
91
+ snapshots: List[PortfolioSnapshot], risk_free_rate: float
92
+ ) -> List[Tuple[float, datetime]]:
93
+ """
94
+ Calculate the rolling Sharpe Ratio over a 365-day window.
95
+
96
+ Args:
97
+ snapshots (List[PortfolioSnapshot]): Time-sorted list of snapshots.
98
+ risk_free_rate (float): Annualized risk-free rate (e.g., 0.03 for 3%).
99
+
100
+ Returns:
101
+ List[Tuple[float, datetime]]: List of (sharpe_ratio, snapshot_date).
102
+ """
103
+ data = [(s.created_at, s.total_value) for s in snapshots]
104
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
105
+ df['created_at'] = pd.to_datetime(df['created_at'])
106
+ df = df.sort_values('created_at').drop_duplicates('created_at')\
107
+ .set_index('created_at')
108
+
109
+ # Resample to daily frequency using last value of the day
110
+ daily_df = df.resample('1D').last().dropna()
111
+
112
+ # Returns as percentage change
113
+ returns_s = daily_df['total_value'].pct_change().dropna()
114
+
115
+ # Rolling Annualised Sharpe
116
+ rolling = returns_s.rolling(window=365)
117
+ rolling_sharpe_s = np.sqrt(365) * (
118
+ rolling.mean() / rolling.std()
119
+ )
120
+
121
+ # Ensure chronological order
122
+ snapshots = sorted(snapshots, key=lambda s: s.created_at)
123
+
124
+ result = []
125
+ for date, sharpe in rolling_sharpe_s.items():
126
+
127
+ if pd.isna(sharpe):
128
+ result.append((sharpe, date))
129
+ continue
130
+
131
+ # Find the corresponding snapshot
132
+ snapshot = next((s for s in snapshots if s.created_at == date), None)
133
+
134
+ if snapshot:
135
+ result.append((sharpe, snapshot.created_at))
136
+
137
+ return result
@@ -0,0 +1,74 @@
1
+ """
2
+ The Sortino Ratio is a risk-adjusted performance metric that tells you how
3
+ much return you're getting per unit of downside risk — a more nuanced
4
+ alternative to the Sharpe Ratio, especially when returns are not
5
+ symmetrically distributed.
6
+
7
+ | **Sortino Ratio** | **Interpretation** |
8
+ |-------------------|----------------------------------------------------------------------|
9
+ | **< 0** | 🚫 Bad — Portfolio underperforms the risk-free rate with downside risk |
10
+ | **0 to 1** | ⚠️ Suboptimal — Low excess return relative to downside risk |
11
+ | **1 to 2** | ✅ Acceptable/Good — Reasonable performance for most portfolios |
12
+ | **2 to 3** | 💪 Strong — Very good risk-adjusted returns |
13
+ | **> 3** | 🌟 Excellent — Rare, may indicate exceptional strategy or overfitting |
14
+
15
+ Formula:
16
+ Sortino Ratio = (Mean Daily Return × Periods Per Year - Risk-Free Rate) /
17
+ (Downside Standard Deviation of Daily Returns × sqrt(Periods Per Year))
18
+
19
+ """
20
+
21
+ from typing import Optional
22
+
23
+ import math
24
+ import numpy as np
25
+ from typing import List
26
+ from investing_algorithm_framework.domain import PortfolioSnapshot
27
+ from .mean_daily_return import get_mean_daily_return
28
+ from .risk_free_rate import get_risk_free_rate_us
29
+ from .standard_deviation import get_downside_std_of_daily_returns
30
+
31
+
32
+ def get_sortino_ratio(
33
+ snapshots: List[PortfolioSnapshot], risk_free_rate: float
34
+ ) -> float:
35
+ """
36
+ Calculate the Sortino Ratio for a given report.
37
+
38
+ The formula for Sortino Ratio is:
39
+ Sortino Ratio = (Annualized Return - Risk-Free Rate) / Downside Standard Deviation
40
+
41
+ Where:
42
+ - Annualized Return is the CAGR of the investment
43
+ - Risk-Free Rate is the return of a risk-free asset (e.g. treasury bills)
44
+ - Downside Standard Deviation is the standard deviation of negative returns
45
+
46
+ Args:
47
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
48
+ from the backtest report.
49
+ risk_free_rate (float): Annual risk-free rate as a decimal
50
+ (e.g., 0.047 for 4.7%).
51
+
52
+ Returns:
53
+ float: The Sortino Ratio.
54
+ """
55
+ snapshots = sorted(snapshots, key=lambda s: s.created_at)
56
+
57
+ if not snapshots:
58
+ return float('inf')
59
+
60
+ mean_daily_return = get_mean_daily_return(snapshots)
61
+ std_downside_daily_return = get_downside_std_of_daily_returns(snapshots)
62
+
63
+ if std_downside_daily_return == 0:
64
+ return float('nan') # or 0.0, depending on preference
65
+
66
+ # Formula: Sharpe Ratio = (Mean Daily Return × Periods Per Year - Risk-Free Rate) /
67
+ # (Standard Deviation of Daily Returns × sqrt(Periods Per Year))
68
+ ratio = (mean_daily_return * 365 - risk_free_rate) / \
69
+ (std_downside_daily_return * math.sqrt(365))
70
+
71
+ if np.float64("inf") == ratio or np.float64("-inf") == ratio:
72
+ return float('inf')
73
+
74
+ return ratio if not np.isnan(ratio) else 0.0
@@ -0,0 +1,157 @@
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
+
112
+ # Resample to daily frequency (end of day)
113
+ daily_df = df.resample("D").last().dropna()
114
+
115
+ # Calculate daily returns
116
+ daily_df["return"] = daily_df["total_value"].pct_change().dropna()
117
+
118
+ if daily_df["return"].empty:
119
+ return 0.0
120
+
121
+ return daily_df["return"].std()
122
+
123
+
124
+ def get_downside_std_of_daily_returns(snapshots):
125
+ """
126
+ Calculate the downside standard deviation of daily returns from a list of snapshots.
127
+ Resamples data to daily frequency using end-of-day values.
128
+
129
+ Args:
130
+ snapshots (List[PortfolioSnapshot]): Snapshots with total_value and created_at.
131
+
132
+ Returns:
133
+ float: Downside standard deviation of daily returns.
134
+ """
135
+ if len(snapshots) < 2:
136
+ return 0.0 # Not enough data
137
+
138
+ # Create DataFrame from snapshots
139
+ data = [(s.created_at, s.total_value) for s in snapshots]
140
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
141
+ df["created_at"] = pd.to_datetime(df["created_at"])
142
+ df = df.drop_duplicates("created_at").set_index("created_at")
143
+ df = df.sort_index()
144
+
145
+ # Resample to daily frequency (end of day)
146
+ daily_df = df.resample("D").last().dropna()
147
+
148
+ # Calculate daily returns
149
+ daily_df["return"] = daily_df["total_value"].pct_change().dropna()
150
+
151
+ # Filter only negative returns for downside deviation
152
+ negative_returns = daily_df["return"][daily_df["return"] < 0]
153
+
154
+ if negative_returns.empty:
155
+ return 0.0
156
+
157
+ return negative_returns.std()