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,468 @@
1
+ from datetime import datetime, timezone
2
+ from uuid import uuid4
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from investing_algorithm_framework.domain import BacktestDateRange, \
8
+ BacktestRun, Portfolio, TimeFrame, PortfolioConfiguration, \
9
+ PortfolioSnapshot, OperationalException, Order, OrderType, OrderStatus, \
10
+ OrderSide, Trade, TradeStatus, DataType
11
+ from investing_algorithm_framework.services import DataProviderService, \
12
+ create_backtest_metrics
13
+
14
+
15
+ # if (
16
+ # portfolio_configurations is None
17
+ # or len(portfolio_configurations) == 0
18
+ # ) and (
19
+ # initial_amount is None
20
+ # or trading_symbol is None
21
+ # or market is None
22
+ # ):
23
+ # raise OperationalException(
24
+ # "No initial amount, trading symbol or market provided "
25
+ # "for the backtest and no portfolio configurations found. "
26
+ # "please register a portfolio configuration "
27
+ # "or specify the initial amount, trading symbol and "
28
+ # "market parameters before running a backtest."
29
+ # )
30
+ #
31
+ # if portfolio_configurations is None \
32
+ # or len(portfolio_configurations) == 0:
33
+ # portfolio_configurations = []
34
+ # portfolio_configurations.append(
35
+ # PortfolioConfiguration(
36
+ # identifier="vector_backtest",
37
+ # market=market,
38
+ # trading_symbol=trading_symbol,
39
+ # initial_balance=initial_amount
40
+ # )
41
+ # )
42
+ #
43
+ # portfolio_configuration = portfolio_configurations[0]
44
+
45
+ class VectorBacktestService:
46
+
47
+ def __init__(
48
+ self, data_provider_service: DataProviderService
49
+ ):
50
+ self.data_provider_service = data_provider_service
51
+
52
+ def run(
53
+ self,
54
+ strategy,
55
+ backtest_date_range: BacktestDateRange,
56
+ portfolio_configuration: PortfolioConfiguration,
57
+ risk_free_rate: float = 0.027,
58
+ dynamic_position_sizing: bool = False,
59
+ ) -> BacktestRun:
60
+ """
61
+ Vectorized backtest for multiple assets using strategy
62
+ buy/sell signals.
63
+
64
+ Args:
65
+ strategy: The strategy to backtest.
66
+ backtest_date_range: The date range for the backtest.
67
+ portfolio_configuration: Portfolio configuration containing
68
+ initial balance, market, and trading symbol.
69
+ risk_free_rate: The risk-free rate to use for the backtest
70
+ metrics. Default is 0.027 (2.7%).
71
+ dynamic_position_sizing: If True, position sizes are recalculated
72
+ at each trade based on current portfolio value (similar to
73
+ event-based backtesting). If False (default), position sizes
74
+ are calculated once at the start based on initial portfolio
75
+ value. Default is False for backward compatibility.
76
+
77
+ Returns:
78
+ BacktestRun: The backtest run containing the results and metrics.
79
+ """
80
+ initial_amount = portfolio_configuration.initial_balance
81
+ trading_symbol = portfolio_configuration.trading_symbol
82
+ portfolio = Portfolio.from_portfolio_configuration(
83
+ portfolio_configuration
84
+ )
85
+
86
+ # Load vectorized backtest data
87
+ data = self.data_provider_service.get_vectorized_backtest_data(
88
+ data_sources=strategy.data_sources,
89
+ start_date=backtest_date_range.start_date,
90
+ end_date=backtest_date_range.end_date
91
+ )
92
+
93
+ # Compute signals from strategy
94
+ buy_signals = strategy.generate_buy_signals(data)
95
+ sell_signals = strategy.generate_sell_signals(data)
96
+
97
+ # Build master index (union of all indices in signal dict)
98
+ index = pd.Index([])
99
+
100
+ most_granular_ohlcv_data_source = \
101
+ self.get_most_granular_ohlcv_data_source(
102
+ strategy.data_sources
103
+ )
104
+
105
+ most_granular_ohlcv_data = self.data_provider_service.get_ohlcv_data(
106
+ symbol=most_granular_ohlcv_data_source.symbol,
107
+ start_date=backtest_date_range.start_date,
108
+ end_date=backtest_date_range.end_date,
109
+ pandas=True
110
+ )
111
+
112
+ # Make sure to filter out the buy and sell signals that are before
113
+ # the backtest start date
114
+ buy_signals = {k: v[v.index >= backtest_date_range.start_date]
115
+ for k, v in buy_signals.items()}
116
+ sell_signals = {k: v[v.index >= backtest_date_range.start_date]
117
+ for k, v in sell_signals.items()}
118
+
119
+ index = index.union(most_granular_ohlcv_data.index)
120
+ index = index.sort_values()
121
+
122
+ # Initialize trades and portfolio values
123
+ trades = []
124
+ orders = []
125
+ granular_ohlcv_data_order_by_symbol = {}
126
+ snapshots = [
127
+ PortfolioSnapshot(
128
+ trading_symbol=trading_symbol,
129
+ portfolio_id=portfolio.identifier,
130
+ created_at=backtest_date_range.start_date,
131
+ unallocated=portfolio_configuration.initial_balance,
132
+ total_value=portfolio_configuration.initial_balance,
133
+ total_net_gain=0.0
134
+ )
135
+ ]
136
+
137
+ # Pre-compute all data needed for each symbol
138
+ symbol_data = {}
139
+ for symbol in buy_signals.keys():
140
+ full_symbol = f"{symbol}/{trading_symbol}"
141
+
142
+ # find PositionSize object
143
+ pos_size_obj = next(
144
+ (p for p in strategy.position_sizes if
145
+ p.symbol == symbol), None
146
+ )
147
+
148
+ if pos_size_obj is None:
149
+ raise OperationalException(
150
+ f"No position size object defined "
151
+ f"for symbol {symbol}, please make sure to "
152
+ f"register a PositionSize object in the strategy."
153
+ )
154
+
155
+ # Load most granular OHLCV data for the symbol
156
+ df = self.data_provider_service.get_ohlcv_data(
157
+ symbol=full_symbol,
158
+ start_date=backtest_date_range.start_date,
159
+ end_date=backtest_date_range.end_date,
160
+ pandas=True
161
+ )
162
+ granular_ohlcv_data_order_by_symbol[full_symbol] = df
163
+
164
+ # Align signals with most granular OHLCV data
165
+ close = df["Close"].reindex(index, method='ffill')
166
+ buy_signal = buy_signals[symbol].reindex(index, fill_value=False)
167
+ sell_signal = sell_signals[symbol].reindex(index, fill_value=False)
168
+
169
+ signal = pd.Series(0, index=index)
170
+ signal[buy_signal] = 1
171
+ signal[sell_signal] = -1
172
+ signal = signal.replace(0, np.nan).ffill().shift(1).fillna(0)
173
+ signal = signal.astype(float)
174
+
175
+ # Calculate initial capital for trade
176
+ # (used when dynamic_position_sizing=False)
177
+ initial_capital_for_trade = pos_size_obj.get_size(
178
+ Portfolio(
179
+ unallocated=portfolio_configuration.initial_balance,
180
+ initial_balance=portfolio_configuration.initial_balance,
181
+ trading_symbol=trading_symbol,
182
+ net_size=0,
183
+ market="BACKTEST",
184
+ identifier="vector_backtest"
185
+ ),
186
+ asset_price=close.iloc[0] if len(close) > 0 else 1.0
187
+ )
188
+
189
+ symbol_data[symbol] = {
190
+ 'full_symbol': full_symbol,
191
+ 'pos_size_obj': pos_size_obj,
192
+ 'close': close,
193
+ 'signal': signal,
194
+ 'initial_capital_for_trade': initial_capital_for_trade,
195
+ 'last_trade': None, # Track open trade per symbol
196
+ }
197
+
198
+ # Shared portfolio state for dynamic position sizing
199
+ current_unallocated = initial_amount
200
+ total_realized_gains = 0.0
201
+ open_trades_value = {} # Track value of open trades per symbol
202
+
203
+ # Process all timestamps in chronological order
204
+ for i in range(len(index)):
205
+ current_date = index[i]
206
+
207
+ # Convert the pd.Timestamp to an utc datetime object
208
+ if isinstance(current_date, pd.Timestamp):
209
+ current_date = current_date.to_pydatetime()
210
+
211
+ if current_date.tzinfo is None:
212
+ current_date = current_date.replace(tzinfo=timezone.utc)
213
+
214
+ # Process each symbol at this timestamp
215
+ for symbol, data in symbol_data.items():
216
+ current_signal = data['signal'].iloc[i]
217
+ current_price = float(data['close'].iloc[i])
218
+ pos_size_obj = data['pos_size_obj']
219
+ last_trade = data['last_trade']
220
+
221
+ # If we are not in a position, and we get a buy signal
222
+ if current_signal == 1 and last_trade is None:
223
+ # Calculate capital for this trade
224
+ if dynamic_position_sizing:
225
+ # Calculate current portfolio value:
226
+ # unallocated + value of all open trades
227
+ open_trades_total = sum(open_trades_value.values())
228
+ current_portfolio_value = \
229
+ current_unallocated + open_trades_total
230
+ capital_for_trade = pos_size_obj.get_size(
231
+ Portfolio(
232
+ unallocated=current_portfolio_value,
233
+ initial_balance=initial_amount,
234
+ trading_symbol=trading_symbol,
235
+ net_size=0,
236
+ market="BACKTEST",
237
+ identifier="vector_backtest"
238
+ ),
239
+ asset_price=current_price
240
+ )
241
+ # Don't exceed available unallocated funds
242
+ capital_for_trade = min(
243
+ capital_for_trade, current_unallocated
244
+ )
245
+ else:
246
+ capital_for_trade = data['initial_capital_for_trade']
247
+
248
+ if capital_for_trade <= 0:
249
+ continue # Skip if no capital available
250
+
251
+ amount = float(capital_for_trade / current_price)
252
+
253
+ # Update shared portfolio state
254
+ if dynamic_position_sizing:
255
+ current_unallocated -= capital_for_trade
256
+
257
+ buy_order = Order(
258
+ id=uuid4(),
259
+ target_symbol=symbol,
260
+ trading_symbol=trading_symbol,
261
+ order_type=OrderType.LIMIT,
262
+ price=current_price,
263
+ amount=amount,
264
+ status=OrderStatus.CLOSED,
265
+ created_at=current_date,
266
+ updated_at=current_date,
267
+ order_side=OrderSide.BUY
268
+ )
269
+ orders.append(buy_order)
270
+ trade = Trade(
271
+ id=uuid4(),
272
+ orders=[buy_order],
273
+ target_symbol=symbol,
274
+ trading_symbol=trading_symbol,
275
+ available_amount=amount,
276
+ remaining=0,
277
+ filled_amount=amount,
278
+ open_price=current_price,
279
+ opened_at=current_date,
280
+ closed_at=None,
281
+ amount=amount,
282
+ status=TradeStatus.OPEN.value,
283
+ cost=capital_for_trade
284
+ )
285
+ data['last_trade'] = trade
286
+ trades.append(trade)
287
+
288
+ # Track open trade value
289
+ if dynamic_position_sizing:
290
+ open_trades_value[symbol] = capital_for_trade
291
+
292
+ # If we are in a position, and we get a sell signal
293
+ if current_signal == -1 and last_trade is not None:
294
+ net_gain_val = (
295
+ current_price - last_trade.open_price
296
+ ) * last_trade.available_amount
297
+
298
+ # Update shared portfolio state
299
+ if dynamic_position_sizing:
300
+ current_unallocated += last_trade.cost + net_gain_val
301
+ total_realized_gains += net_gain_val
302
+ if symbol in open_trades_value:
303
+ del open_trades_value[symbol]
304
+
305
+ sell_order = Order(
306
+ id=uuid4(),
307
+ target_symbol=symbol,
308
+ trading_symbol=trading_symbol,
309
+ order_type=OrderType.LIMIT,
310
+ price=current_price,
311
+ amount=last_trade.available_amount,
312
+ status=OrderStatus.CLOSED,
313
+ created_at=current_date,
314
+ updated_at=current_date,
315
+ order_side=OrderSide.SELL
316
+ )
317
+ orders.append(sell_order)
318
+ trade_orders = last_trade.orders
319
+ trade_orders.append(sell_order)
320
+ last_trade.update(
321
+ {
322
+ "orders": trade_orders,
323
+ "closed_at": current_date,
324
+ "status": TradeStatus.CLOSED.value,
325
+ "updated_at": current_date,
326
+ "net_gain": net_gain_val
327
+ }
328
+ )
329
+ data['last_trade'] = None
330
+
331
+ # Update open trade values at each timestamp for
332
+ # accurate portfolio value
333
+ if dynamic_position_sizing:
334
+ for symbol, data in symbol_data.items():
335
+ if data['last_trade'] is not None:
336
+ current_price = float(data['close'].iloc[i])
337
+ open_trades_value[symbol] = (
338
+ data['last_trade'].available_amount * current_price
339
+ )
340
+
341
+ unallocated = initial_amount
342
+ total_net_gain = 0.0
343
+ open_trades = []
344
+
345
+ # Create portfolio snapshots
346
+ for ts in index:
347
+ allocated = 0
348
+ interval_datetime = pd.Timestamp(ts).to_pydatetime()
349
+ interval_datetime = interval_datetime.replace(tzinfo=timezone.utc)
350
+
351
+ for trade in trades:
352
+
353
+ if trade.opened_at == interval_datetime:
354
+ # Snapshot taken at the moment a trade is opened
355
+ unallocated -= trade.cost
356
+ open_trades.append(trade)
357
+
358
+ if trade.closed_at == interval_datetime:
359
+ # Snapshot taken at the moment a trade is closed
360
+ unallocated += trade.cost + trade.net_gain
361
+ total_net_gain += trade.net_gain
362
+ open_trades.remove(trade)
363
+
364
+ for open_trade in open_trades:
365
+ ohlcv = granular_ohlcv_data_order_by_symbol[
366
+ f"{open_trade.target_symbol}/{trading_symbol}"
367
+ ]
368
+ try:
369
+ price = ohlcv.loc[:ts, "Close"].iloc[-1]
370
+ open_trade.last_reported_price = price
371
+ except IndexError:
372
+ continue # skip if no price yet
373
+
374
+ allocated += open_trade.filled_amount * price
375
+
376
+ # total_value = invested_value + unallocated
377
+ # total_net_gain = total_value - initial_amount
378
+ snapshots.append(
379
+ PortfolioSnapshot(
380
+ portfolio_id=portfolio.identifier,
381
+ created_at=interval_datetime,
382
+ unallocated=unallocated,
383
+ total_value=unallocated + allocated,
384
+ total_net_gain=total_net_gain
385
+ )
386
+ )
387
+
388
+ unique_symbols = set()
389
+ for trade in trades:
390
+ unique_symbols.add(trade.target_symbol)
391
+
392
+ number_of_trades_closed = len(
393
+ [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
394
+ )
395
+ number_of_trades_open = len(
396
+ [t for t in trades if TradeStatus.OPEN.equals(t.status)]
397
+ )
398
+ # Create a backtest run object
399
+ run = BacktestRun(
400
+ trading_symbol=trading_symbol,
401
+ initial_unallocated=initial_amount,
402
+ number_of_runs=1,
403
+ portfolio_snapshots=snapshots,
404
+ trades=trades,
405
+ orders=orders,
406
+ positions=[],
407
+ created_at=datetime.now(timezone.utc),
408
+ backtest_start_date=backtest_date_range.start_date,
409
+ backtest_end_date=backtest_date_range.end_date,
410
+ backtest_date_range_name=backtest_date_range.name,
411
+ number_of_days=(
412
+ backtest_date_range.end_date - backtest_date_range.end_date
413
+ ).days,
414
+ number_of_trades=len(trades),
415
+ number_of_orders=len(orders),
416
+ number_of_trades_closed=number_of_trades_closed,
417
+ number_of_trades_open=number_of_trades_open,
418
+ number_of_positions=len(unique_symbols),
419
+ symbols=list(buy_signals.keys())
420
+ )
421
+
422
+ # Create backtest metrics
423
+ run.backtest_metrics = create_backtest_metrics(
424
+ run, risk_free_rate=risk_free_rate
425
+ )
426
+ return run
427
+
428
+ @staticmethod
429
+ def get_most_granular_ohlcv_data_source(data_sources):
430
+ """
431
+ Get the most granular data source from a list of data sources.
432
+
433
+ Args:
434
+ data_sources: List of data sources.
435
+
436
+ Returns:
437
+ The most granular data source.
438
+ """
439
+ granularity_order = {
440
+ TimeFrame.ONE_MINUTE: 1,
441
+ TimeFrame.FIVE_MINUTE: 5,
442
+ TimeFrame.FIFTEEN_MINUTE: 15,
443
+ TimeFrame.ONE_HOUR: 60,
444
+ TimeFrame.TWO_HOUR: 120,
445
+ TimeFrame.FOUR_HOUR: 240,
446
+ TimeFrame.TWELVE_HOUR: 720,
447
+ TimeFrame.ONE_DAY: 1440,
448
+ TimeFrame.ONE_WEEK: 10080,
449
+ TimeFrame.ONE_MONTH: 43200
450
+ }
451
+
452
+ most_granular = None
453
+ highest_granularity = float('inf')
454
+
455
+ ohlcv_data_sources = [
456
+ ds for ds in data_sources if DataType.OHLCV.equals(ds.data_type)
457
+ ]
458
+
459
+ if len(ohlcv_data_sources) == 0:
460
+ raise OperationalException("No OHLCV data sources found")
461
+
462
+ for source in ohlcv_data_sources:
463
+
464
+ if granularity_order[source.time_frame] < highest_granularity:
465
+ highest_granularity = granularity_order[source.time_frame]
466
+ most_granular = source
467
+
468
+ return most_granular
@@ -1,19 +1,136 @@
1
- from .order_service import OrderService
2
- from .portfolio_service import PortfolioService
3
- from .position_service import PositionService
4
- from .position_cost_service import PositionCostService
1
+ from .trade_order_evaluator import BacktestTradeOrderEvaluator, \
2
+ TradeOrderEvaluator, DefaultTradeOrderEvaluator
3
+ from .configuration_service import ConfigurationService
4
+ from .market_credential_service import MarketCredentialService
5
+ from .data_providers import DataProviderService
6
+ from .order_service import OrderService, OrderBacktestService, \
7
+ OrderExecutorLookup
8
+ from .portfolios import PortfolioService, BacktestPortfolioService, \
9
+ PortfolioConfigurationService, PortfolioSyncService, \
10
+ PortfolioSnapshotService, PortfolioProviderLookup
11
+ from .positions import PositionService, PositionSnapshotService
5
12
  from .repository_service import RepositoryService
6
- from .strategy_orchestrator_service import StrategyOrchestratorService
7
- from .portfolio_configuration_service import PortfolioConfigurationService
8
- from .market_data_service import MarketDataService
13
+ from .trade_service import TradeService, TradeStopLossService, \
14
+ TradeTakeProfitService
15
+ from .metrics import get_annual_volatility, get_mean_daily_return, \
16
+ get_sortino_ratio, get_drawdown_series, get_max_drawdown, \
17
+ get_equity_curve, get_price_efficiency_ratio, get_sharpe_ratio, \
18
+ get_profit_factor, get_cumulative_profit_factor_series, \
19
+ get_rolling_profit_factor_series, get_daily_returns_std, \
20
+ get_cagr, get_standard_deviation_returns, get_mean_yearly_return, \
21
+ get_standard_deviation_downside_returns, \
22
+ get_total_return, get_cumulative_exposure, get_exposure_ratio, \
23
+ get_yearly_returns, get_monthly_returns, get_best_year, \
24
+ get_best_month, get_worst_year, get_worst_month, get_best_trade, \
25
+ get_worst_trade, get_average_yearly_return, get_average_trade_gain, \
26
+ get_average_trade_loss, get_average_monthly_return, \
27
+ get_percentage_winning_months, get_average_trade_duration, \
28
+ get_trade_frequency, get_win_rate, get_win_loss_ratio, \
29
+ get_calmar_ratio, get_max_drawdown_absolute, get_current_win_loss_ratio, \
30
+ get_max_drawdown_duration, get_max_daily_drawdown, get_trades_per_day, \
31
+ get_trades_per_year, get_average_monthly_return_losing_months, \
32
+ get_average_monthly_return_winning_months, get_percentage_winning_years, \
33
+ get_rolling_sharpe_ratio, create_backtest_metrics, get_total_growth, \
34
+ get_total_loss, get_risk_free_rate_us, get_median_trade_return, \
35
+ get_average_trade_return, get_cumulative_return, \
36
+ get_cumulative_return_series, get_average_trade_size, \
37
+ get_positive_trades, get_negative_trades, get_number_of_trades, \
38
+ get_current_win_rate, get_current_average_trade_return, \
39
+ get_current_average_trade_loss, get_current_average_trade_duration, \
40
+ get_current_average_trade_gain, create_backtest_metrics_for_backtest
9
41
 
10
42
  __all__ = [
11
- "StrategyOrchestratorService",
43
+ "get_mean_daily_return",
44
+ "get_daily_returns_std",
12
45
  "OrderService",
13
46
  "RepositoryService",
14
47
  "PortfolioService",
15
48
  "PositionService",
16
49
  "PortfolioConfigurationService",
17
- "MarketDataService",
18
- "PositionCostService"
50
+ "OrderBacktestService",
51
+ "ConfigurationService",
52
+ "PortfolioSyncService",
53
+ "PortfolioSnapshotService",
54
+ "PositionSnapshotService",
55
+ "MarketCredentialService",
56
+ "BacktestPortfolioService",
57
+ "TradeService",
58
+ "DataProviderService",
59
+ "OrderExecutorLookup",
60
+ "BacktestTradeOrderEvaluator",
61
+ "PortfolioProviderLookup",
62
+ "TradeOrderEvaluator",
63
+ "DefaultTradeOrderEvaluator",
64
+ "get_risk_free_rate_us",
65
+ "get_annual_volatility",
66
+ "get_sortino_ratio",
67
+ "get_drawdown_series",
68
+ "get_max_drawdown",
69
+ "get_equity_curve",
70
+ "get_price_efficiency_ratio",
71
+ "get_sharpe_ratio",
72
+ "get_profit_factor",
73
+ "get_cumulative_profit_factor_series",
74
+ "get_rolling_profit_factor_series",
75
+ "get_sharpe_ratio",
76
+ "get_cagr",
77
+ "get_standard_deviation_returns",
78
+ "get_standard_deviation_downside_returns",
79
+ "get_max_drawdown_absolute",
80
+ "get_total_return",
81
+ "get_cumulative_exposure",
82
+ "get_exposure_ratio",
83
+ "get_average_trade_duration",
84
+ "get_win_rate",
85
+ "get_win_loss_ratio",
86
+ "get_calmar_ratio",
87
+ "get_trade_frequency",
88
+ "get_yearly_returns",
89
+ "get_monthly_returns",
90
+ "get_best_year",
91
+ "get_best_month",
92
+ "get_worst_year",
93
+ "get_worst_month",
94
+ "get_best_trade",
95
+ "get_worst_trade",
96
+ "get_average_yearly_return",
97
+ "get_average_trade_loss",
98
+ "get_average_monthly_return",
99
+ "get_percentage_winning_months",
100
+ "get_average_trade_duration",
101
+ "get_trade_frequency",
102
+ "get_win_rate",
103
+ "get_win_loss_ratio",
104
+ "get_calmar_ratio",
105
+ "get_max_drawdown_duration",
106
+ "get_max_daily_drawdown",
107
+ "get_trades_per_day",
108
+ "get_trades_per_year",
109
+ "get_average_monthly_return_losing_months",
110
+ "get_average_monthly_return_winning_months",
111
+ "get_percentage_winning_years",
112
+ "get_rolling_sharpe_ratio",
113
+ "get_total_growth",
114
+ "create_backtest_metrics",
115
+ "get_total_loss",
116
+ "get_median_trade_return",
117
+ "get_average_trade_gain",
118
+ "get_average_trade_size",
119
+ "get_average_trade_return",
120
+ "get_positive_trades",
121
+ "get_negative_trades",
122
+ "get_number_of_trades",
123
+ "get_cumulative_return",
124
+ "get_cumulative_return_series",
125
+ "get_current_win_loss_ratio",
126
+ "get_current_win_rate",
127
+ "get_current_win_loss_ratio",
128
+ "get_current_average_trade_loss",
129
+ "get_current_average_trade_duration",
130
+ "get_current_average_trade_gain",
131
+ "get_current_average_trade_return",
132
+ "create_backtest_metrics_for_backtest",
133
+ "TradeStopLossService",
134
+ "TradeTakeProfitService",
135
+ "get_mean_yearly_return"
19
136
  ]