investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (256) hide show
  1. investing_algorithm_framework/__init__.py +168 -45
  2. investing_algorithm_framework/app/__init__.py +32 -1
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +1933 -589
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1725 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/strategy.py +664 -84
  41. investing_algorithm_framework/app/task.py +5 -3
  42. investing_algorithm_framework/app/web/__init__.py +2 -1
  43. investing_algorithm_framework/app/web/create_app.py +4 -2
  44. investing_algorithm_framework/cli/__init__.py +0 -0
  45. investing_algorithm_framework/cli/cli.py +226 -0
  46. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  47. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  48. investing_algorithm_framework/cli/initialize_app.py +603 -0
  49. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  50. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  51. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  52. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  53. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  55. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  56. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  58. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  59. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  60. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  61. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  62. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  63. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  64. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  65. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  66. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  67. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  68. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  69. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  70. investing_algorithm_framework/create_app.py +40 -6
  71. investing_algorithm_framework/dependency_container.py +72 -56
  72. investing_algorithm_framework/domain/__init__.py +71 -47
  73. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  74. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  75. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  76. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  77. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  78. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  79. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  81. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  82. investing_algorithm_framework/domain/config.py +59 -91
  83. investing_algorithm_framework/domain/constants.py +13 -38
  84. investing_algorithm_framework/domain/data_provider.py +334 -0
  85. investing_algorithm_framework/domain/data_structures.py +3 -2
  86. investing_algorithm_framework/domain/exceptions.py +51 -1
  87. investing_algorithm_framework/domain/models/__init__.py +17 -12
  88. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  89. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  90. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  91. investing_algorithm_framework/domain/models/event.py +35 -0
  92. investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
  93. investing_algorithm_framework/domain/models/order/order.py +77 -83
  94. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  95. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  96. investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
  97. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
  98. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  99. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  100. investing_algorithm_framework/domain/models/position/position.py +12 -0
  101. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  102. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  104. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  105. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  106. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  107. investing_algorithm_framework/domain/models/time_frame.py +37 -0
  108. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  109. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  110. investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
  111. investing_algorithm_framework/domain/models/trade/trade.py +295 -171
  112. investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
  113. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  114. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  115. investing_algorithm_framework/domain/order_executor.py +112 -0
  116. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  117. investing_algorithm_framework/domain/services/__init__.py +2 -9
  118. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
  119. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  120. investing_algorithm_framework/domain/strategy.py +1 -29
  121. investing_algorithm_framework/domain/utils/__init__.py +12 -7
  122. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  123. investing_algorithm_framework/domain/utils/dates.py +57 -0
  124. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  125. investing_algorithm_framework/domain/utils/polars.py +53 -0
  126. investing_algorithm_framework/domain/utils/random.py +29 -0
  127. investing_algorithm_framework/download_data.py +108 -0
  128. investing_algorithm_framework/infrastructure/__init__.py +31 -18
  129. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  130. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  131. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  132. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  133. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  134. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  135. investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
  136. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
  137. investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
  138. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  139. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  140. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  141. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
  142. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
  143. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  144. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  145. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  146. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  147. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  148. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  149. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  150. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  151. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  152. investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
  153. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  154. investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
  155. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  156. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  157. investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
  158. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  159. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  160. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  161. investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
  162. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  163. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  164. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  165. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  166. investing_algorithm_framework/services/__init__.py +113 -16
  167. investing_algorithm_framework/services/backtesting/__init__.py +0 -7
  168. investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
  169. investing_algorithm_framework/services/configuration_service.py +77 -11
  170. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  171. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  172. investing_algorithm_framework/services/market_credential_service.py +16 -1
  173. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  174. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  175. investing_algorithm_framework/services/metrics/beta.py +0 -0
  176. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  177. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  178. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  179. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  180. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  181. investing_algorithm_framework/services/metrics/generate.py +358 -0
  182. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  183. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  184. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  185. investing_algorithm_framework/services/metrics/returns.py +452 -0
  186. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  187. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  188. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  189. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  190. investing_algorithm_framework/services/metrics/trades.py +500 -0
  191. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  192. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  193. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  194. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  195. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  196. investing_algorithm_framework/services/order_service/__init__.py +3 -1
  197. investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
  198. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  199. investing_algorithm_framework/services/order_service/order_service.py +407 -326
  200. investing_algorithm_framework/services/portfolios/__init__.py +3 -1
  201. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
  202. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
  203. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  204. investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
  205. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
  206. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
  207. investing_algorithm_framework/services/positions/__init__.py +7 -0
  208. investing_algorithm_framework/services/positions/position_service.py +210 -0
  209. investing_algorithm_framework/services/repository_service.py +8 -2
  210. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  211. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  212. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  213. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  214. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  215. investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
  216. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  217. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  218. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  219. investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
  220. investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
  221. investing_algorithm_framework/app/algorithm.py +0 -1105
  222. investing_algorithm_framework/domain/graphs.py +0 -382
  223. investing_algorithm_framework/domain/metrics/__init__.py +0 -6
  224. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
  225. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
  226. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  227. investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
  228. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
  229. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  230. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  231. investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
  232. investing_algorithm_framework/domain/services/market_service.py +0 -153
  233. investing_algorithm_framework/domain/singleton.py +0 -9
  234. investing_algorithm_framework/domain/utils/backtesting.py +0 -472
  235. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
  236. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
  237. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
  238. investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
  239. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  240. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
  241. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  242. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  243. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
  244. investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
  245. investing_algorithm_framework/services/backtesting/graphs.py +0 -61
  246. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
  247. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
  248. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
  249. investing_algorithm_framework/services/position_service.py +0 -31
  250. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
  251. investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
  252. investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
  253. /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
  254. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  255. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  256. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
@@ -0,0 +1,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
@@ -1,7 +1,9 @@
1
1
  from .order_backtest_service import OrderBacktestService
2
2
  from .order_service import OrderService
3
+ from .order_executor_lookup import OrderExecutorLookup
3
4
 
4
5
  __all__ = [
5
6
  "OrderService",
6
- "OrderBacktestService"
7
+ "OrderBacktestService",
8
+ "OrderExecutorLookup",
7
9
  ]
@@ -2,11 +2,8 @@ import logging
2
2
 
3
3
  import polars as pl
4
4
 
5
- from investing_algorithm_framework.domain import BACKTESTING_INDEX_DATETIME, \
6
- OrderStatus, BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, \
7
- OperationalException, OrderSide, Order
8
- from investing_algorithm_framework.services.market_data_source_service \
9
- import BacktestMarketDataSourceService
5
+ from investing_algorithm_framework.domain import INDEX_DATETIME, \
6
+ OrderStatus, OrderSide, Order, DataType
10
7
  from .order_service import OrderService
11
8
 
12
9
  logger = logging.getLogger("investing_algorithm_framework")
@@ -17,90 +14,86 @@ class OrderBacktestService(OrderService):
17
14
  def __init__(
18
15
  self,
19
16
  order_repository,
20
- position_repository,
17
+ trade_service,
18
+ position_service,
21
19
  portfolio_repository,
22
20
  portfolio_configuration_service,
23
21
  portfolio_snapshot_service,
24
22
  configuration_service,
25
- market_data_source_service: BacktestMarketDataSourceService,
26
23
  ):
27
- super(OrderService, self).__init__(order_repository)
28
- self.order_repository = order_repository
29
- self.position_repository = position_repository
30
- self.portfolio_repository = portfolio_repository
31
- self.portfolio_configuration_service = portfolio_configuration_service
32
- self.portfolio_snapshot_service = portfolio_snapshot_service
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
33
  self.configuration_service = configuration_service
34
- self._market_data_source_service: BacktestMarketDataSourceService = \
35
- market_data_source_service
36
34
 
37
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()
38
50
  # Make sure the created_at is set to the current backtest time
39
- data["created_at"] = self.configuration_service\
40
- .config[BACKTESTING_INDEX_DATETIME]
51
+ data["created_at"] = config[INDEX_DATETIME]
52
+ data["updated_at"] = config[INDEX_DATETIME]
41
53
  # Call super to have standard behavior
42
54
  return super(OrderBacktestService, self)\
43
55
  .create(data, execute, validate, sync)
44
56
 
45
- def execute_order(self, order_id, portfolio):
46
- order = self.get(order_id)
47
- order = self.update(
48
- order_id,
49
- {
50
- "status": OrderStatus.OPEN.value,
51
- "remaining": order.remaining,
52
- "updated_at": self.configuration_service
53
- .config[BACKTESTING_INDEX_DATETIME]
54
- }
55
- )
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
+ ]
56
64
  return order
57
65
 
58
- def check_pending_orders(self):
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
+ """
59
79
  pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
60
- logger.info(f"Checking {len(pending_orders)} open orders")
61
- config = self.configuration_service.get_config()
80
+ meta_data = market_data["metadata"]
62
81
 
63
82
  for order in pending_orders:
64
- symbol = f"{order.target_symbol.upper()}" \
65
- f"/{order.trading_symbol.upper()}"
66
- position = self.position_repository.get(order.position_id)
67
- portfolio = self.portfolio_repository.get(position.portfolio_id)
68
- timeframe = None
69
-
70
- if BACKTESTING_PENDING_ORDER_CHECK_INTERVAL in \
71
- self.configuration_service.config:
72
- timeframe = self.configuration_service\
73
- .config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]
74
-
75
- if not self._market_data_source_service\
76
- .is_ohlcv_data_source_present(
77
- symbol=symbol,
78
- market=portfolio.market,
79
- time_frame=timeframe
80
- ):
81
- raise OperationalException(
82
- f"OHLCV data source not found for {symbol} "
83
- f"and market {portfolio.market} for order check "
84
- f"time frame "
85
- f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]}. "
86
- f"Cannot check pending orders for symbol {symbol} "
87
- f"with market {portfolio.market}. Please add a ohlcv data"
88
- f"source for {symbol} and market {portfolio.market} with "
89
- f"time frame "
90
- f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]} "
91
- )
83
+ ohlcv_meta_data = meta_data[DataType.OHLCV]
92
84
 
93
- df = self._market_data_source_service.get_ohlcv(
94
- symbol=symbol,
95
- market=portfolio.market,
96
- time_frame=timeframe,
97
- to_timestamp=self.configuration_service.config.get(
98
- BACKTESTING_INDEX_DATETIME
99
- ),
100
- from_timestamp=order.get_created_at(),
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]
101
93
  )
94
+ data = market_data[identifier]
102
95
 
103
- if self.has_executed(order, df):
96
+ if self.has_executed(order, data):
104
97
  self.update(
105
98
  order.id,
106
99
  {
@@ -108,10 +101,9 @@ class OrderBacktestService(OrderService):
108
101
  "filled": order.get_amount(),
109
102
  "remaining": 0,
110
103
  "updated_at": self.configuration_service
111
- .config[BACKTESTING_INDEX_DATETIME]
104
+ .config[INDEX_DATETIME]
112
105
  }
113
106
  )
114
- break
115
107
 
116
108
  def cancel_order(self, order):
117
109
  self.check_pending_orders()
@@ -126,11 +118,11 @@ class OrderBacktestService(OrderService):
126
118
  "status": OrderStatus.CANCELED.value,
127
119
  "remaining": 0,
128
120
  "updated_at": self.configuration_service
129
- .config[BACKTESTING_INDEX_DATETIME]
121
+ .config[INDEX_DATETIME]
130
122
  }
131
123
  )
132
124
 
133
- def has_executed(self, order, ohlcv_data_frame):
125
+ def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
134
126
  """
135
127
  Check if the order has executed based on the OHLCV data.
136
128
 
@@ -138,7 +130,7 @@ class OrderBacktestService(OrderService):
138
130
  order price. Example: If the order price is 1000 and the low price
139
131
  drops below or equals 1000, the order is executed. This simulates the
140
132
  situation where a buyer is willing to pay a higher price than the
141
- the lowest price in the ohlcv data.
133
+ lowest price in the ohlcv data.
142
134
 
143
135
  A sell order is executed if the high price goes above or equals the
144
136
  order price. Example: If the order price is 1000 and the high price
@@ -146,27 +138,22 @@ class OrderBacktestService(OrderService):
146
138
  situation where a seller is willing to accept a higher price for its
147
139
  sell order.
148
140
 
149
- :param order: Order object
150
- :param ohlcv_data_frame: OHLCV data frame
151
- :return: True if the order has executed, False otherwise
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
152
148
  """
153
149
 
154
150
  # Extract attributes from the order object
155
151
  created_at = order.get_created_at()
156
152
  order_side = order.get_order_side()
157
153
  order_price = order.get_price()
158
- column_type = ohlcv_data_frame['Datetime'].dtype
159
-
160
- if isinstance(column_type, pl.Datetime):
161
- ohlcv_data_after_order = ohlcv_data_frame.filter(
162
- pl.col('Datetime') >= created_at
163
- )
164
- else:
165
- ohlcv_data_after_order = ohlcv_data_frame.filter(
166
- pl.col('Datetime') >= created_at.strftime(
167
- self.configuration_service.config["DATETIME_FORMAT"]
168
- )
169
- )
154
+ ohlcv_data_after_order = ohlcv_data_frame.filter(
155
+ pl.col('Datetime') >= created_at
156
+ )
170
157
 
171
158
  # Check if the order execution conditions are met
172
159
  if OrderSide.BUY.equals(order_side):
@@ -185,7 +172,7 @@ class OrderBacktestService(OrderService):
185
172
 
186
173
  if created_at is None:
187
174
  created_at = self.configuration_service \
188
- .config[BACKTESTING_INDEX_DATETIME]
175
+ .config[INDEX_DATETIME]
189
176
 
190
177
  super(OrderBacktestService, self)\
191
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 = []