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

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

Potentially problematic release.


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

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,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 = []