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,118 @@
1
+ """
2
+ Volatility is a statistical measure of the dispersion of returns for a
3
+ given portfolio. In finance, it is commonly used as a proxy for risk.
4
+ This function calculates the standard deviation of daily log returns and
5
+ annualizes it, giving an estimate of how much the portfolio's value
6
+ fluctuates on a yearly basis.
7
+
8
+ | **Annual Volatility** | **Risk Level (Standalone)** | **Context Matters: Sharpe Ratio Impact** | **Comments** |
9
+ | --------------------- | --------------------------- | ---------------------------------------- | ----------- |
10
+ | **< 5%** | Very Low Risk | Sharpe > 2.0 = Excellent<br>Sharpe < 0.5 = Poor | Low volatility is great unless returns are negative |
11
+ | **5% – 10%** | Low Risk | Sharpe > 1.0 = Good<br>Sharpe < 0.3 = Mediocre | Typical for conservative portfolios |
12
+ | **10% – 15%** | Moderate Risk | Sharpe > 0.8 = Good<br>Sharpe < 0.2 = Risky | S&P 500 benchmark; quality matters |
13
+ | **15% – 25%** | High Risk | Sharpe > 0.6 = Acceptable<br>Sharpe < 0.0 = Avoid | **Example: 30% CAGR + 23% vol = Sharpe ~1.3 = Excellent** |
14
+ | **> 25%** | Very High Risk | Sharpe > 0.4 = Maybe acceptable<br>Sharpe < 0.0 = Dangerous | Only viable with strong positive returns |
15
+
16
+
17
+ Key takeaway: Don't interpret volatility in isolation. Always calculate
18
+ and compare the Sharpe Ratio to assess true strategy quality.
19
+ Your 30% CAGR with 23% volatility is exceptional because the return far outweighs the risk taken.
20
+
21
+ """
22
+
23
+ from typing import List
24
+
25
+ import pandas as pd
26
+ import numpy as np
27
+
28
+ from investing_algorithm_framework.domain import PortfolioSnapshot
29
+
30
+
31
+ def get_annual_volatility(
32
+ snapshots: List[PortfolioSnapshot],
33
+ trading_days_per_year=365
34
+ ) -> float:
35
+ """
36
+ Calculate the annualized volatility of portfolio net values.
37
+
38
+ !Important Note:
39
+
40
+ Volatility measures variability, not direction. For example:
41
+
42
+ A standard deviation of 0.238 (23.8%) means returns swing
43
+ wildly around their average, but it doesn't tell you if that average
44
+ is positive or negative.
45
+
46
+ Two scenarios with the same 23.8% volatility:
47
+ Mean return = +15% per year, Std = 23.8%
48
+ 16% chance of losing >8.8% (15% - 23.8%)
49
+ 16% chance of gaining >38.8% (15% + 23.8%)
50
+ This is excellent — high growth with swings
51
+
52
+ Mean return = -5% per year, Std = 23.8%
53
+ 16% chance of losing >28.8% (-5% - 23.8%)
54
+ 16% chance of gaining >18.8% (-5% + 23.8%)
55
+ This is terrible — losing money with high risk
56
+
57
+ To assess if "always good returns with high std" is perfect, you need
58
+ to consider risk-adjusted metrics like the Sharpe Ratio:
59
+ Sharpe Ratio = (Mean Return - Risk-Free Rate) / Volatility
60
+ Higher is better; tells you return per unit of risk taken
61
+
62
+ Args:
63
+ snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
64
+ from the backtest report.
65
+ trading_days_per_year (int): Number of trading days in a year.
66
+
67
+ Returns:
68
+ Float: Annualized volatility as a float
69
+ """
70
+
71
+ if len(snapshots) < 2:
72
+ return 0.0
73
+
74
+ # Build DataFrame from snapshots
75
+ records = [
76
+ (snapshot.total_value, snapshot.created_at) for snapshot in snapshots
77
+ ]
78
+ df = pd.DataFrame(records, columns=['total_value', 'created_at'])
79
+ df['created_at'] = pd.to_datetime(df['created_at'])
80
+ df = df.set_index('created_at').sort_index().drop_duplicates()
81
+
82
+ # Resample to daily frequency, taking the last value of each day
83
+ df_daily = df.resample('D').last()
84
+ df_daily = df_daily.dropna()
85
+
86
+ if len(df_daily) < 2:
87
+ return 0.0
88
+
89
+ # Filter out non-positive values before calculating log returns
90
+ # Log returns are only valid for positive portfolio values
91
+ df_daily = df_daily[df_daily['total_value'] > 0]
92
+
93
+ if len(df_daily) < 2:
94
+ return 0.0
95
+
96
+ # Calculate log returns on daily data
97
+ # Add a small epsilon to avoid log(0) and replace inf/nan values
98
+ price_ratios = df_daily['total_value'] / df_daily['total_value'].shift(1)
99
+
100
+ # Filter out invalid price ratios (zero, negative, or NaN)
101
+ df_daily['log_return'] = np.log(price_ratios)
102
+
103
+ # Replace inf and -inf with NaN, then drop them
104
+ df_daily['log_return'] = df_daily['log_return'].replace([np.inf, -np.inf], np.nan)
105
+ df_daily = df_daily.dropna()
106
+
107
+ if len(df_daily) < 2:
108
+ return 0.0
109
+
110
+ # Calculate daily volatility (standard deviation of daily returns)
111
+ daily_volatility = df_daily['log_return'].std()
112
+
113
+ # Handle edge case where std might be NaN
114
+ if pd.isna(daily_volatility):
115
+ return 0.0
116
+
117
+ # Annualize using trading days per year
118
+ return daily_volatility * np.sqrt(trading_days_per_year)
@@ -0,0 +1,177 @@
1
+ """
2
+ | Metric | High Value Means... | Weakness if Used Alone |
3
+ | ------------------ | --------------------------- | ----------------------------------- |
4
+ | **Win Rate** | Many trades are profitable | Doesn't say how big wins/losses are |
5
+ | **Win/Loss Ratio** | Big wins relative to losses | Doesn’t say how *often* you win |
6
+
7
+
8
+ Example of Non-Overlap:
9
+ Strategy A: 90% win rate, but average win is $1, average loss is $10 → not profitable.
10
+
11
+ Strategy B: 30% win rate, but average win is $300, average loss is $50 → highly profitable.
12
+
13
+ | Win Rate | Win/Loss Ratio | Comment |
14
+ | ----------------- | -------------- | ---------------------------------- |
15
+ | High (>60%) | <1 | Can still be profitable |
16
+ | Moderate (40-60%) | \~1 or >1 | Ideal sweet spot |
17
+ | Low (<40%) | >1 | Possible if big wins offset losses |
18
+
19
+
20
+ Practical Example:
21
+ * Win rate 40% with win/loss ratio 2: Good — you win less often but your
22
+ wins are twice as big.
23
+ * Win rate 60% with win/loss ratio 0.7: Also good — you win often
24
+ but your wins are smaller than losses.
25
+
26
+ What’s “good”?
27
+ * Typical win/loss ratio ranges from 0.5 to 3 depending on strategy style.
28
+ * Many profitable traders target win/loss ratio between 1.5 and 2.5.
29
+ * Very aggressive strategies might have a lower win rate but
30
+ higher win/loss ratio.
31
+ """
32
+
33
+ from typing import List
34
+ from investing_algorithm_framework.domain import TradeStatus, Trade
35
+
36
+
37
+ def get_win_rate(trades: List[Trade]) -> float:
38
+ """
39
+ Calculate the win rate of the portfolio based on the backtest report.
40
+
41
+ Win Rate is defined as the percentage of trades that were profitable.
42
+ The percentage of trades that are profitable.
43
+
44
+ Formula:
45
+ Win Rate = Number of Profitable Trades / Total Number of Trades
46
+
47
+ Example: If 60 out of 100 trades are profitable, the win rate is 60%.
48
+
49
+ Args:
50
+ trades (List[Trade]): List of trades from the backtest report.
51
+
52
+ Returns:
53
+ float: The win rate as a percentage (e.g., o.75 for 75% win rate).
54
+ """
55
+ trades = [
56
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
57
+ ]
58
+ positive_trades = sum(1 for trade in trades if trade.net_gain > 0)
59
+ total_trades = len(trades)
60
+
61
+ if total_trades == 0:
62
+ return 0.0
63
+
64
+ return positive_trades / total_trades
65
+
66
+ def get_current_win_rate(trades: List[Trade]) -> float:
67
+ """
68
+ Calculate the current win rate of the portfolio based on a list
69
+ of recent trades.
70
+
71
+ Current Win Rate is defined as the percentage of the most recent trades
72
+ that were profitable. This metric also includes trades that are still open.
73
+
74
+ Formula:
75
+ Current Win Rate = Number of Profitable Recent Trades
76
+ / Total Number of Recent Trades
77
+
78
+ Args:
79
+ trades (List[Trade]): List of recent trades.
80
+
81
+ Returns:
82
+ float: The current win rate as a percentage (e.g., 0.75 for
83
+ 75% win rate).
84
+ """
85
+ if not trades:
86
+ return 0.0
87
+
88
+ positive_trades = sum(1 for trade in trades if trade.net_gain_absolute > 0)
89
+ total_trades = len(trades)
90
+
91
+ return positive_trades / total_trades
92
+
93
+
94
+ def get_win_loss_ratio(trades: List[Trade]) -> float:
95
+ """
96
+ Calculate the win/loss ratio of the portfolio based on the backtest report.
97
+
98
+ Win/Loss Ratio is defined as the average profit of winning trades divided by
99
+ the average loss of losing trades.
100
+
101
+ Formula:
102
+ Win/Loss Ratio = Average Profit of Winning Trades
103
+ / Average Loss of Losing Trades
104
+
105
+ Example: If the average profit of winning trades is $200 and the
106
+ average loss of losing trades is $100, the win/loss ratio is 2.0.
107
+
108
+ Args:
109
+ trades (List[Trade]): List of trades from the backtest report.
110
+
111
+ Returns:
112
+ float: The win/loss ratio.
113
+ """
114
+ trades = [
115
+ trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
116
+ ]
117
+
118
+ if not trades:
119
+ return 0.0
120
+
121
+ # Separate winning and losing trades
122
+ winning_trades = [t for t in trades if t.net_gain > 0]
123
+ losing_trades = [t for t in trades if t.net_gain < 0]
124
+
125
+ if not winning_trades or not losing_trades:
126
+ return 0.0
127
+
128
+ # Compute averages
129
+ avg_win = sum(t.net_gain for t in winning_trades) / len(winning_trades)
130
+ avg_loss = abs(
131
+ sum(t.net_gain for t in losing_trades) / len(losing_trades))
132
+
133
+ # Avoid division by zero
134
+ if avg_loss == 0:
135
+ return float('inf')
136
+
137
+ return avg_win / avg_loss
138
+
139
+
140
+ def get_current_win_loss_ratio(trades: List[Trade]) -> float:
141
+ """
142
+ Calculate the current win/loss ratio of the portfolio based on a list
143
+ of recent trades.
144
+
145
+ Current Win/Loss Ratio is defined as the average profit of winning
146
+ recent trades divided by the average loss of losing recent trades.
147
+ This metric also includes trades that are still open.
148
+
149
+ Formula:
150
+ Current Win/Loss Ratio = Average Profit of Winning Recent Trades
151
+ / Average Loss of Losing Recent Trades
152
+ Args:
153
+ trades (List[Trade]): List of recent trades.
154
+
155
+ Returns:
156
+ float: The current win/loss ratio.
157
+ """
158
+ if not trades:
159
+ return 0.0
160
+
161
+ # Separate winning and losing trades
162
+ winning_trades = [t for t in trades if t.net_gain_absolute > 0]
163
+ losing_trades = [t for t in trades if t.net_gain_absolute < 0]
164
+
165
+ if not winning_trades or not losing_trades:
166
+ return 0.0
167
+
168
+ # Compute averages
169
+ avg_win = sum(t.net_gain_absolute for t in winning_trades) / len(winning_trades)
170
+ avg_loss = abs(
171
+ sum(t.net_gain_absolute for t in losing_trades) / len(losing_trades))
172
+
173
+ # Avoid division by zero
174
+ if avg_loss == 0:
175
+ return float('inf')
176
+
177
+ return avg_win / avg_loss
@@ -0,0 +1,9 @@
1
+ from .order_backtest_service import OrderBacktestService
2
+ from .order_service import OrderService
3
+ from .order_executor_lookup import OrderExecutorLookup
4
+
5
+ __all__ = [
6
+ "OrderService",
7
+ "OrderBacktestService",
8
+ "OrderExecutorLookup",
9
+ ]
@@ -0,0 +1,178 @@
1
+ import logging
2
+
3
+ import polars as pl
4
+
5
+ from investing_algorithm_framework.domain import INDEX_DATETIME, \
6
+ OrderStatus, OrderSide, Order, DataType
7
+ from .order_service import OrderService
8
+
9
+ logger = logging.getLogger("investing_algorithm_framework")
10
+
11
+
12
+ class OrderBacktestService(OrderService):
13
+
14
+ def __init__(
15
+ self,
16
+ order_repository,
17
+ trade_service,
18
+ position_service,
19
+ portfolio_repository,
20
+ portfolio_configuration_service,
21
+ portfolio_snapshot_service,
22
+ configuration_service,
23
+ ):
24
+ super().__init__(
25
+ configuration_service=configuration_service,
26
+ order_repository=order_repository,
27
+ position_service=position_service,
28
+ portfolio_repository=portfolio_repository,
29
+ portfolio_configuration_service=portfolio_configuration_service,
30
+ portfolio_snapshot_service=portfolio_snapshot_service,
31
+ trade_service=trade_service,
32
+ )
33
+ self.configuration_service = configuration_service
34
+
35
+ def create(self, data, execute=True, validate=True, sync=True) -> Order:
36
+ """
37
+ Override the create method to set the created_at and
38
+ updated_at attributes to the current backtest time.
39
+
40
+ Args:
41
+ data (dict): Dictionary containing the order data
42
+ execute (bool): Flag to execute the order
43
+ validate (bool): Flag to validate the order
44
+ sync (bool): Flag to sync the order
45
+
46
+ Returns:
47
+ Order: Created order object
48
+ """
49
+ config = self.configuration_service.get_config()
50
+ # Make sure the created_at is set to the current backtest time
51
+ data["created_at"] = config[INDEX_DATETIME]
52
+ data["updated_at"] = config[INDEX_DATETIME]
53
+ # Call super to have standard behavior
54
+ return super(OrderBacktestService, self)\
55
+ .create(data, execute, validate, sync)
56
+
57
+ def execute_order(self, order, portfolio):
58
+ order.status = OrderStatus.OPEN.value
59
+ order.remaining = order.get_amount()
60
+ order.filled = 0
61
+ order.updated_at = self.configuration_service.config[
62
+ INDEX_DATETIME
63
+ ]
64
+ return order
65
+
66
+ def check_pending_orders(self, market_data):
67
+ """
68
+ Function to check if any pending orders have executed. It querys the
69
+ open orders and checks if the order has executed based on the OHLCV
70
+ data. If the order has executed, the order status is set to CLOSED
71
+ and the filled amount is set to the order amount.
72
+
73
+ Args:
74
+ market_data (dict): Dictionary containing the market data
75
+
76
+ Returns:
77
+ None
78
+ """
79
+ pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
80
+ meta_data = market_data["metadata"]
81
+
82
+ for order in pending_orders:
83
+ ohlcv_meta_data = meta_data[DataType.OHLCV]
84
+
85
+ if order.get_symbol() not in ohlcv_meta_data:
86
+ continue
87
+
88
+ timeframes = ohlcv_meta_data[order.get_symbol()].keys()
89
+ sorted_timeframes = sorted(timeframes)
90
+ most_granular_interval = sorted_timeframes[0]
91
+ identifier = (
92
+ ohlcv_meta_data[order.get_symbol()][most_granular_interval]
93
+ )
94
+ data = market_data[identifier]
95
+
96
+ if self.has_executed(order, data):
97
+ self.update(
98
+ order.id,
99
+ {
100
+ "status": OrderStatus.CLOSED.value,
101
+ "filled": order.get_amount(),
102
+ "remaining": 0,
103
+ "updated_at": self.configuration_service
104
+ .config[INDEX_DATETIME]
105
+ }
106
+ )
107
+
108
+ def cancel_order(self, order):
109
+ self.check_pending_orders()
110
+ order = self.order_repository.get(order.id)
111
+
112
+ if order is not None:
113
+
114
+ if OrderStatus.OPEN.equals(order.status):
115
+ self.update(
116
+ order.id,
117
+ {
118
+ "status": OrderStatus.CANCELED.value,
119
+ "remaining": 0,
120
+ "updated_at": self.configuration_service
121
+ .config[INDEX_DATETIME]
122
+ }
123
+ )
124
+
125
+ def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
126
+ """
127
+ Check if the order has executed based on the OHLCV data.
128
+
129
+ A buy order is executed if the low price drops below or equals the
130
+ order price. Example: If the order price is 1000 and the low price
131
+ drops below or equals 1000, the order is executed. This simulates the
132
+ situation where a buyer is willing to pay a higher price than the
133
+ lowest price in the ohlcv data.
134
+
135
+ A sell order is executed if the high price goes above or equals the
136
+ order price. Example: If the order price is 1000 and the high price
137
+ goes above or equals 1000, the order is executed. This simulates the
138
+ situation where a seller is willing to accept a higher price for its
139
+ sell order.
140
+
141
+ Args:
142
+ order: Order object
143
+ ohlcv_data_frame: OHLCV data frame
144
+ True if the order has executed, False otherwise
145
+
146
+ Returns:
147
+ bool: True if the order has executed, False otherwise
148
+ """
149
+
150
+ # Extract attributes from the order object
151
+ created_at = order.get_created_at()
152
+ order_side = order.get_order_side()
153
+ order_price = order.get_price()
154
+ ohlcv_data_after_order = ohlcv_data_frame.filter(
155
+ pl.col('Datetime') >= created_at
156
+ )
157
+
158
+ # Check if the order execution conditions are met
159
+ if OrderSide.BUY.equals(order_side):
160
+ # Check if the low price drops below or equals the order price
161
+ if (ohlcv_data_after_order['Low'] <= order_price).any():
162
+ return True
163
+ elif OrderSide.SELL.equals(order_side):
164
+ # Check if the high price goes above or equals the order price
165
+ if (ohlcv_data_after_order['High'] >= order_price).any():
166
+ return True
167
+
168
+ # If conditions are not met, return False
169
+ return False
170
+
171
+ def create_snapshot(self, portfolio_id, created_at=None):
172
+
173
+ if created_at is None:
174
+ created_at = self.configuration_service \
175
+ .config[INDEX_DATETIME]
176
+
177
+ super(OrderBacktestService, self)\
178
+ .create_snapshot(portfolio_id, created_at=created_at)
@@ -0,0 +1,110 @@
1
+ from collections import defaultdict
2
+ from typing import List
3
+
4
+ from investing_algorithm_framework.domain import OrderExecutor, \
5
+ ImproperlyConfigured
6
+
7
+
8
+ class OrderExecutorLookup:
9
+ """
10
+ Efficient lookup for order executors based on market in O(1) time.
11
+
12
+ Attributes:
13
+ order_executors (List[OrderExecutor]): List of order executors
14
+ order_executor_lookup (dict): Dictionary to store the lookup
15
+ for order executors based on market.
16
+ """
17
+ def __init__(self, order_executors=[]):
18
+ self.order_executors = order_executors
19
+
20
+ # These will be our lookup tables
21
+ self.order_executor_lookup = defaultdict()
22
+
23
+ def add_order_executor(self, order_executor: OrderExecutor):
24
+ """
25
+ Add an order executor to the lookup.
26
+
27
+ Args:
28
+ order_executor (OrderExecutor): The order executor to be added.
29
+
30
+ Returns:
31
+ None
32
+ """
33
+ self.order_executors.append(order_executor)
34
+
35
+ def register_order_executor_for_market(self, market) -> None:
36
+ """
37
+ Register an order executor for a specific market.
38
+ This method will create a lookup table for efficient access to
39
+ order executors based on market. It will use the
40
+ order executors that are currently registered in the
41
+ order_executors list. The lookup table will be a dictionary
42
+ where the key is the market and the value is the portfolio provider.
43
+
44
+ This method will also check if the portfolio provider supports
45
+ the market. If no portfolio provider is found for the market,
46
+ it will raise an ImproperlyConfigured exception.
47
+
48
+ If multiple order executors are found for the market,
49
+ it will sort them by priority and pick the best one.
50
+
51
+ Args:
52
+ market:
53
+
54
+ Returns:
55
+ None
56
+ """
57
+ matches = []
58
+
59
+ for order_executor in self.order_executors:
60
+
61
+ if order_executor.supports_market(market):
62
+ matches.append(order_executor)
63
+
64
+ if len(matches) == 0:
65
+ raise ImproperlyConfigured(
66
+ f"No portfolio provider found for market "
67
+ f"{market}. Cannot configure portfolio."
68
+ f" Please make sure that you have registered a portfolio "
69
+ f"provider for the market you are trying to use"
70
+ )
71
+
72
+ # Sort by priority and pick the best one
73
+ best_provider = sorted(matches, key=lambda x: x.priority)[0]
74
+ self.order_executor_lookup[market] = best_provider
75
+
76
+ def get_order_executor(self, market: str):
77
+ """
78
+ Get the order executor for a specific market.
79
+ This method will return the order executor for the market
80
+ that was registered in the lookup table. If no order executor
81
+ was found for the market, it will return None.
82
+
83
+ Args:
84
+ market:
85
+
86
+ Returns:
87
+ OrderExecutor: The order executor for the market.
88
+ """
89
+ return self.order_executor_lookup.get(market, None)
90
+
91
+ def get_all(self) -> List[OrderExecutor]:
92
+ """
93
+ Get all order executors.
94
+ This method will return all order executors that are currently
95
+ registered in the order_executors list.
96
+
97
+ Returns:
98
+ List[OrderExecutor]: A list of all order executors.
99
+ """
100
+ return self.order_executors
101
+
102
+ def reset(self):
103
+ """
104
+ Function to reset the order executor lookup table
105
+
106
+ Returns:
107
+ None
108
+ """
109
+ self.order_executor_lookup = defaultdict()
110
+ self.order_executors = []