investing-algorithm-framework 1.5__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 (276) hide show
  1. investing_algorithm_framework/__init__.py +192 -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 +29 -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 +2220 -379
  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/time_metrics_table.py +80 -0
  31. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  32. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  33. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  34. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  35. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
  36. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  37. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  39. investing_algorithm_framework/app/strategy.py +867 -60
  40. investing_algorithm_framework/app/task.py +5 -3
  41. investing_algorithm_framework/app/web/__init__.py +2 -1
  42. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  43. investing_algorithm_framework/app/web/controllers/orders.py +3 -2
  44. investing_algorithm_framework/app/web/controllers/positions.py +2 -2
  45. investing_algorithm_framework/app/web/create_app.py +4 -2
  46. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  47. investing_algorithm_framework/cli/__init__.py +0 -0
  48. investing_algorithm_framework/cli/cli.py +231 -0
  49. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  50. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  51. investing_algorithm_framework/cli/initialize_app.py +603 -0
  52. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  53. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  55. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  56. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  58. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  59. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  60. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  61. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  62. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  63. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  64. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  65. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  66. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  67. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  68. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  69. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  70. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  71. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  72. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  73. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  74. investing_algorithm_framework/create_app.py +40 -7
  75. investing_algorithm_framework/dependency_container.py +100 -47
  76. investing_algorithm_framework/domain/__init__.py +97 -30
  77. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  78. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  79. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  81. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  82. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  83. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  84. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  87. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  88. investing_algorithm_framework/domain/config.py +59 -136
  89. investing_algorithm_framework/domain/constants.py +18 -37
  90. investing_algorithm_framework/domain/data_provider.py +334 -0
  91. investing_algorithm_framework/domain/data_structures.py +42 -0
  92. investing_algorithm_framework/domain/exceptions.py +51 -1
  93. investing_algorithm_framework/domain/models/__init__.py +26 -19
  94. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  95. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  96. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  97. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  98. investing_algorithm_framework/domain/models/event.py +35 -0
  99. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  100. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  101. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  102. investing_algorithm_framework/domain/models/order/order.py +198 -65
  103. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  104. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  105. investing_algorithm_framework/domain/models/portfolio/__init__.py +6 -2
  106. investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
  107. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
  108. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  109. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  110. investing_algorithm_framework/domain/models/position/position.py +20 -0
  111. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  112. investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
  113. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  114. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  115. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  116. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  117. investing_algorithm_framework/domain/models/strategy_profile.py +19 -141
  118. investing_algorithm_framework/domain/models/time_frame.py +94 -98
  119. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  120. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  121. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  122. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  123. investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
  124. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  125. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  126. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  127. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  128. investing_algorithm_framework/domain/order_executor.py +112 -0
  129. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  130. investing_algorithm_framework/domain/services/__init__.py +11 -0
  131. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  132. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  133. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  134. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  135. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  136. investing_algorithm_framework/domain/strategy.py +1 -29
  137. investing_algorithm_framework/domain/utils/__init__.py +15 -5
  138. investing_algorithm_framework/domain/utils/csv.py +22 -0
  139. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  140. investing_algorithm_framework/domain/utils/dates.py +57 -0
  141. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  142. investing_algorithm_framework/domain/utils/polars.py +53 -0
  143. investing_algorithm_framework/domain/utils/random.py +29 -0
  144. investing_algorithm_framework/download_data.py +244 -0
  145. investing_algorithm_framework/infrastructure/__init__.py +37 -11
  146. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  147. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  148. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  149. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  150. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  151. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  152. investing_algorithm_framework/infrastructure/models/__init__.py +7 -3
  153. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  154. investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
  155. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  156. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  157. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  158. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -2
  159. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
  160. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
  161. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  162. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  163. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  164. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  165. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  166. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  167. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  168. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  169. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  170. investing_algorithm_framework/infrastructure/repositories/__init__.py +10 -4
  171. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  172. investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
  173. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  174. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  175. investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
  176. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  177. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  178. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  179. investing_algorithm_framework/infrastructure/services/__init__.py +9 -4
  180. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  181. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  182. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  183. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  184. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  185. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  186. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  187. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  188. investing_algorithm_framework/services/__init__.py +123 -15
  189. investing_algorithm_framework/services/configuration_service.py +77 -11
  190. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  191. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  192. investing_algorithm_framework/services/market_credential_service.py +40 -0
  193. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  194. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  195. investing_algorithm_framework/services/metrics/beta.py +0 -0
  196. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  197. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  198. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  199. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  200. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  201. investing_algorithm_framework/services/metrics/generate.py +358 -0
  202. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  203. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  204. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  205. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  206. investing_algorithm_framework/services/metrics/returns.py +452 -0
  207. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  208. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  209. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  210. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  211. investing_algorithm_framework/services/metrics/trades.py +473 -0
  212. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  213. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  214. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  215. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  216. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  217. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  218. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  219. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  220. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  221. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  222. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  223. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  224. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  225. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  226. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  227. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  228. investing_algorithm_framework/services/positions/__init__.py +7 -0
  229. investing_algorithm_framework/services/positions/position_service.py +210 -0
  230. investing_algorithm_framework/services/repository_service.py +8 -2
  231. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  232. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  233. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  234. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  235. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  237. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  238. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  239. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  240. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  241. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  242. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  243. investing_algorithm_framework/app/algorithm.py +0 -630
  244. investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
  245. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  246. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  247. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
  248. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  249. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  250. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  251. investing_algorithm_framework/domain/models/trade.py +0 -78
  252. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  253. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  254. investing_algorithm_framework/domain/singleton.py +0 -9
  255. investing_algorithm_framework/domain/utils/backtesting.py +0 -82
  256. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  257. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  258. investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
  259. investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
  260. investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
  261. investing_algorithm_framework/services/backtest_service.py +0 -268
  262. investing_algorithm_framework/services/market_data_service.py +0 -77
  263. investing_algorithm_framework/services/order_backtest_service.py +0 -122
  264. investing_algorithm_framework/services/order_service.py +0 -752
  265. investing_algorithm_framework/services/portfolio_service.py +0 -164
  266. investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
  267. investing_algorithm_framework/services/position_cost_service.py +0 -5
  268. investing_algorithm_framework/services/position_service.py +0 -63
  269. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
  270. investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
  271. investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
  272. investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
  273. investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
  274. /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
  275. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  276. {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,452 @@
1
+ from typing import List, Tuple
2
+ from datetime import datetime, date
3
+
4
+ import pandas as pd
5
+
6
+ from investing_algorithm_framework.domain import PortfolioSnapshot, Trade, \
7
+ OperationalException
8
+
9
+
10
+ def get_monthly_returns(snapshots: List[PortfolioSnapshot]) -> List[Tuple[float, datetime]]:
11
+ """
12
+ Calculate the monthly returns from a list of portfolio snapshots.
13
+
14
+ Monthly return is calculated as the percentage change in portfolio value
15
+ from the end of one month to the end of the next month.
16
+
17
+ Args:
18
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
19
+
20
+ Returns:
21
+ List[Tuple[float, datetime]]: A list of tuples containing the monthly return
22
+ and the corresponding month.
23
+ """
24
+
25
+ # Create DataFrame from snapshots
26
+ data = [(s.created_at, s.total_value) for s in snapshots]
27
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
28
+ df['created_at'] = pd.to_datetime(df['created_at'])
29
+ df = df.sort_values('created_at').drop_duplicates('created_at')\
30
+ .set_index('created_at')
31
+
32
+ # Resample to monthly frequency using last value of the month
33
+ monthly_df = df.resample('ME').last().dropna()
34
+ monthly_df['return'] = monthly_df['total_value'].pct_change()
35
+ monthly_df = monthly_df.dropna()
36
+
37
+ # Ensure returns are Python floats, not numpy floats
38
+ monthly_returns = [
39
+ (float(row['return']), row.name) for _, row in monthly_df.iterrows()
40
+ ]
41
+ return monthly_returns
42
+
43
+
44
+ def get_yearly_returns(snapshots: List[PortfolioSnapshot]) -> List[Tuple[float, date]]:
45
+ """
46
+ Calculate the yearly returns from a list of portfolio snapshots.
47
+
48
+ Yearly return is calculated as the percentage change in portfolio value
49
+ from the end of one year to the end of the next year.
50
+
51
+ Args:
52
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
53
+
54
+ Returns:
55
+ List[Tuple[float, date]]: A list of tuples containing the yearly return
56
+ and the corresponding year.
57
+ """
58
+
59
+ # Create DataFrame from snapshots
60
+ data = [(s.created_at, s.total_value) for s in snapshots]
61
+ df = pd.DataFrame(data, columns=["created_at", "total_value"])
62
+ df['created_at'] = pd.to_datetime(df['created_at'])
63
+ df = df.sort_values('created_at').drop_duplicates('created_at')\
64
+ .set_index('created_at')
65
+
66
+ # Remove timezone information if present to avoid warning
67
+ if df.index.tz is not None:
68
+ df.index = df.index.tz_localize(None)
69
+
70
+ # Resample to yearly frequency using last value of the year
71
+ yearly_df = df.resample('YE').last().dropna()
72
+ yearly_df['return'] = yearly_df['total_value'].pct_change()
73
+ yearly_df = yearly_df.dropna()
74
+
75
+ # Yearly returns with date objects only representing the year
76
+ yearly_df.index = yearly_df.index.to_period('Y').to_timestamp()
77
+ yearly_returns = [
78
+ (float(row['return']), row.name) for _, row in yearly_df.iterrows()
79
+ ]
80
+ return yearly_returns
81
+
82
+
83
+ def get_percentage_winning_months(snapshots: List[PortfolioSnapshot]) -> float:
84
+ """
85
+ Calculate the percentage of winning months from portfolio snapshots.
86
+
87
+ A winning month is defined as a month where the portfolio value at the end
88
+ of the month is greater than at the start of the month.
89
+
90
+ Args:
91
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
92
+
93
+ Returns:
94
+ float: The percentage of winning months.
95
+ """
96
+
97
+ monthly_returns = get_monthly_returns(snapshots)
98
+ winning_months = sum(1 for r, _ in monthly_returns if r > 0)
99
+
100
+ if not monthly_returns:
101
+ return 0.0
102
+
103
+ return (winning_months / len(monthly_returns))
104
+
105
+
106
+ def get_best_month(snapshots: List[PortfolioSnapshot]) -> Tuple[float, datetime]:
107
+ """
108
+ Get the best month in terms of return from portfolio snapshots.
109
+
110
+ Args:
111
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
112
+
113
+ Returns:
114
+ Tuple[float, datetime]: The best monthly return and the corresponding month.
115
+ """
116
+
117
+ monthly_returns = get_monthly_returns(snapshots)
118
+
119
+ if not monthly_returns:
120
+ return 0.0, None
121
+
122
+ return max(monthly_returns, key=lambda x: x[0])
123
+
124
+ def get_worst_month(snapshots: List[PortfolioSnapshot]) -> Tuple[float, datetime]:
125
+ """
126
+ Get the worst month in terms of return from portfolio snapshots.
127
+
128
+ Args:
129
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
130
+
131
+ Returns:
132
+ Tuple[float, datetime]: The worst monthly return and the corresponding month.
133
+ """
134
+
135
+ monthly_returns = get_monthly_returns(snapshots)
136
+
137
+ if not monthly_returns:
138
+ return 0.0, None
139
+
140
+ return min(monthly_returns, key=lambda x: x[0])
141
+
142
+ def get_best_year(
143
+ snapshots: List[PortfolioSnapshot]
144
+ ) -> Tuple[float, datetime]:
145
+ """
146
+ Get the best year in terms of return from portfolio snapshots.
147
+
148
+ Args:
149
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
150
+
151
+ Returns:
152
+ Tuple[float, datetime]: The best yearly return and the corresponding year.
153
+ """
154
+
155
+ yearly_returns = get_yearly_returns(snapshots)
156
+
157
+ if not yearly_returns:
158
+ return None, None
159
+
160
+ return max(yearly_returns, key=lambda x: x[0])
161
+
162
+
163
+ def get_worst_year(
164
+ snapshots: List[PortfolioSnapshot]
165
+ ) -> Tuple[float, date]:
166
+ """
167
+ Get the worst year in terms of return from portfolio snapshots.
168
+
169
+ Args:
170
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
171
+
172
+ Returns:
173
+ Tuple[float, datetime]: The worst yearly return and the corresponding year.
174
+ """
175
+
176
+ yearly_returns = get_yearly_returns(snapshots)
177
+
178
+ if not yearly_returns:
179
+ return None, None
180
+
181
+ return min(yearly_returns, key=lambda x: x[0])
182
+
183
+
184
+ def get_average_monthly_return(snapshots: List[PortfolioSnapshot]) -> float:
185
+ """
186
+ Calculate the average monthly return from portfolio snapshots.
187
+
188
+ The average monthly return is calculated as the mean of all monthly returns.
189
+
190
+ Args:
191
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
192
+
193
+ Returns:
194
+ float: The average monthly return as a percentage.
195
+ """
196
+
197
+ monthly_returns = get_monthly_returns(snapshots)
198
+
199
+ if not monthly_returns:
200
+ return 0.0
201
+
202
+ return sum(r for r, _ in monthly_returns) / len(monthly_returns)
203
+
204
+ def get_average_monthly_return_winning_months(snapshots: List[PortfolioSnapshot]) -> float:
205
+ """
206
+ Calculate the average monthly return from winning months in portfolio snapshots.
207
+
208
+ The average monthly return is calculated as the mean of all monthly returns
209
+ where the return is positive.
210
+
211
+ Args:
212
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
213
+
214
+ Returns:
215
+ float: The average monthly return from winning months as a percentage.
216
+ """
217
+
218
+ monthly_returns = get_monthly_returns(snapshots)
219
+ winning_months = [r for r, _ in monthly_returns if r > 0]
220
+
221
+ if not winning_months:
222
+ return 0.0
223
+
224
+ return sum(winning_months) / len(winning_months)
225
+
226
+ def get_average_monthly_return_losing_months(snapshots: List[PortfolioSnapshot]) -> float:
227
+ """
228
+ Calculate the average monthly return from losing months in portfolio snapshots.
229
+
230
+ The average monthly return is calculated as the mean of all monthly returns
231
+ where the return is negative.
232
+
233
+ Args:
234
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
235
+
236
+ Returns:
237
+ float: The average monthly return from losing months as a percentage.
238
+ """
239
+
240
+ monthly_returns = get_monthly_returns(snapshots)
241
+ losing_months = [r for r, _ in monthly_returns if r < 0]
242
+
243
+ if not losing_months:
244
+ return 0.0
245
+
246
+ return sum(losing_months) / len(losing_months)
247
+
248
+
249
+ def get_average_yearly_return(snapshots: List[PortfolioSnapshot]) -> float:
250
+ """
251
+ Calculate the average yearly return from portfolio snapshots.
252
+
253
+ The average yearly return is calculated as the mean of all yearly returns.
254
+
255
+ Args:
256
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
257
+
258
+ Returns:
259
+ float: The average yearly return as a percentage.
260
+ """
261
+
262
+ yearly_returns = get_yearly_returns(snapshots)
263
+
264
+ if not yearly_returns:
265
+ return 0.0
266
+
267
+ return sum(r for r, _ in yearly_returns) / len(yearly_returns)
268
+
269
+
270
+ def get_total_return(
271
+ snapshots: List[PortfolioSnapshot]
272
+ ) -> Tuple[float, float]:
273
+ """
274
+ Calculate the total return from portfolio snapshots.
275
+
276
+ The total return is calculated as the percentage change in portfolio value
277
+ from the first snapshot to the last snapshot.
278
+
279
+ Args:
280
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
281
+
282
+ Returns:
283
+ Tuple[Float, Float]: First number is the absolute return and the
284
+ second number is the percentage total return
285
+ """
286
+
287
+ if not snapshots or len(snapshots) < 2:
288
+ return 0.0, 0.0
289
+
290
+ initial_value = snapshots[0].total_value
291
+ final_value = snapshots[-1].total_value
292
+
293
+ if initial_value == 0:
294
+ return 0.0, 0.0
295
+
296
+ absolute_return = final_value - initial_value
297
+ percentage = (absolute_return / initial_value)
298
+ return absolute_return, percentage
299
+
300
+
301
+ def get_total_loss(
302
+ snapshots: List[PortfolioSnapshot]
303
+ ) -> Tuple[float, float]:
304
+ """
305
+ Calculate the total loss from portfolio snapshots.
306
+
307
+ The total loss is calculated as the percentage change in portfolio value
308
+ from the first snapshot to the last snapshot, only if there is a loss.
309
+
310
+ Args:
311
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
312
+
313
+ Returns:
314
+ Tuple[Float, Float]: First number is the absolute loss and the
315
+ second number is the percentage total loss
316
+ """
317
+
318
+ if not snapshots or len(snapshots) < 2:
319
+ return 0.0, 0.0
320
+
321
+ initial_value = snapshots[0].total_value
322
+ final_value = snapshots[-1].total_value
323
+
324
+ if initial_value == 0:
325
+ return 0.0, 0.0
326
+
327
+ absolute_return = final_value - initial_value
328
+
329
+ if absolute_return >= 0:
330
+ return 0.0, 0.0
331
+
332
+ percentage = (absolute_return / initial_value)
333
+ return absolute_return, percentage
334
+
335
+
336
+ def get_total_growth(
337
+ snapshots: List[PortfolioSnapshot]
338
+ ) -> Tuple[float, float]:
339
+ """
340
+ Calculate the total growth from portfolio snapshots.
341
+
342
+ The total return is calculated as the percentage change in portfolio value
343
+ from the first snapshot to the last snapshot added to the initial value.
344
+
345
+ Args:
346
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
347
+
348
+ Returns:
349
+ Tuple[Float, Float]: First number is the absolute return and the
350
+ second number is the percentage total return
351
+ """
352
+
353
+ if not snapshots or len(snapshots) < 2:
354
+ return 0.0, 0.0
355
+
356
+ initial_value = snapshots[0].total_value
357
+ final_value = snapshots[-1].total_value
358
+
359
+ if initial_value == 0:
360
+ return 0.0, 0.0
361
+
362
+ growth = final_value - initial_value
363
+ growth_percentage = (growth / initial_value)
364
+ return growth, growth_percentage
365
+
366
+
367
+ def get_percentage_winning_years(snapshots: List[PortfolioSnapshot]) -> float:
368
+ """
369
+ Calculate the percentage of winning years from portfolio snapshots.
370
+
371
+ A winning year is defined as a year where the portfolio value at the end
372
+ of the year is greater than at the start of the year.
373
+
374
+ Args:
375
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
376
+
377
+ Returns:
378
+ float: The percentage of winning years.
379
+ """
380
+
381
+ yearly_returns = get_yearly_returns(snapshots)
382
+ winning_years = sum(1 for r, _ in yearly_returns if r > 0)
383
+
384
+ if not yearly_returns:
385
+ return 0.0
386
+
387
+ return winning_years / len(yearly_returns)
388
+
389
+
390
+ def get_final_value(snapshots: List[PortfolioSnapshot]) -> float:
391
+ """
392
+ Calculate the final portfolio value from portfolio snapshots.
393
+
394
+ Args:
395
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
396
+
397
+ Returns:
398
+ float: The final portfolio value.
399
+ """
400
+
401
+ if not snapshots:
402
+ return 0.0
403
+
404
+ return snapshots[-1].total_value
405
+
406
+
407
+ def get_cumulative_return(snapshots: list[PortfolioSnapshot]) -> float:
408
+ """
409
+ Calculate cumulative return over the full period of snapshots.
410
+ Returns a single float (e.g., 0.25 for +25%).
411
+ """
412
+ if len(snapshots) < 2:
413
+ return 0.0
414
+
415
+ # Sort snapshots by date
416
+ snapshots = sorted(snapshots, key=lambda s: s.created_at)
417
+
418
+ start_value = snapshots[0].total_value
419
+ end_value = snapshots[-1].total_value
420
+
421
+ if start_value == 0:
422
+ return 0.0
423
+
424
+ return (end_value / start_value) - 1
425
+
426
+
427
+ def get_cumulative_return_series(
428
+ snapshots: list[PortfolioSnapshot]
429
+ ) -> List[Tuple[float, datetime]]:
430
+ """
431
+ Calculate cumulative returns from a list of PortfolioSnapshot objects.
432
+
433
+ Args:
434
+ snapshots (list[PortfolioSnapshot]): List of snapshots ordered by time.
435
+
436
+ Returns:
437
+ List[Tuple[float, datetime]]: Cumulative returns for each snapshot.
438
+ """
439
+
440
+ # Ensure snapshots are sorted by date
441
+ snapshots = sorted(snapshots, key=lambda s: s.get_created_at())
442
+
443
+ initial_value = snapshots[0].get_total_value()
444
+ if initial_value == 0:
445
+ raise ValueError("Initial portfolio value cannot be zero.")
446
+
447
+ cumulative_returns = []
448
+ for snap in snapshots:
449
+ cum_return = (snap.get_total_value() / initial_value) - 1
450
+ cumulative_returns.append((cum_return, snap.created_at))
451
+
452
+ return cumulative_returns
@@ -0,0 +1,28 @@
1
+ import yfinance as yf
2
+ import logging
3
+
4
+
5
+ logger = logging.getLogger("investing_algorithm_framework")
6
+
7
+
8
+ def get_risk_free_rate_us():
9
+ """
10
+ Retrieves the US 10-year Treasury yield from Yahoo Finance.
11
+
12
+ Returns:
13
+ float or None: The latest yield as a decimal (e.g., 0.0423 for 4.23%), or None if unavailable.
14
+ """
15
+ try:
16
+ ten_year = yf.Ticker("^TNX")
17
+ hist = ten_year.history(period="5d")
18
+
19
+ if hist.empty or "Close" not in hist.columns:
20
+ logger.warning("Risk-free rate data is unavailable or malformed.")
21
+ return None
22
+
23
+ latest_yield = hist["Close"].dropna().iloc[-1] / 100
24
+ return latest_yield
25
+
26
+ except Exception as e:
27
+ logger.warning(f"Could not retrieve risk-free rate: {e}")
28
+ return None
@@ -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
+ import math
48
+ from datetime import datetime
49
+ from typing import List, Tuple
50
+
51
+ import numpy as np
52
+ import pandas as pd
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