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
@@ -1,40 +1,45 @@
1
- from .config import Config, Environment
1
+ from .config import Environment, DEFAULT_LOGGING_CONFIG, \
2
+ AWS_LAMBDA_LOGGING_CONFIG
2
3
  from .constants import ITEMIZE, ITEMIZED, PER_PAGE, PAGE, ENVIRONMENT, \
3
4
  DATABASE_DIRECTORY_PATH, DATABASE_NAME, DEFAULT_PER_PAGE_VALUE, \
4
5
  DEFAULT_PAGE_VALUE, SQLALCHEMY_DATABASE_URI, RESOURCE_DIRECTORY, \
5
6
  DATETIME_FORMAT, DATETIME_FORMAT_BACKTESTING, BACKTESTING_FLAG, \
6
- BACKTESTING_INDEX_DATETIME, BACKTESTING_START_DATE, CCXT_DATETIME_FORMAT, \
7
+ BACKTESTING_START_DATE, CCXT_DATETIME_FORMAT, \
7
8
  BACKTEST_DATA_DIRECTORY_NAME, TICKER_DATA_TYPE, OHLCV_DATA_TYPE, \
8
- CURRENT_UTC_DATETIME, BACKTESTING_END_DATE, SYMBOLS, \
9
- CCXT_DATETIME_FORMAT_WITH_TIMEZONE, RESERVED_BALANCES, \
10
- BACKTESTING_PENDING_ORDER_CHECK_INTERVAL, APP_MODE
9
+ CURRENT_UTC_DATETIME, BACKTESTING_END_DATE, \
10
+ CCXT_DATETIME_FORMAT_WITH_TIMEZONE, \
11
+ APP_MODE, DATABASE_DIRECTORY_NAME, BACKTESTING_INITIAL_AMOUNT, \
12
+ APPLICATION_DIRECTORY, SNAPSHOT_INTERVAL, AWS_S3_STATE_BUCKET_NAME, \
13
+ LAST_SNAPSHOT_DATETIME, DATA_DIRECTORY, INDEX_DATETIME, \
14
+ DATETIME_FORMAT_FILE_NAME, DEFAULT_DATETIME_FORMAT
15
+ from .data_provider import DataProvider
11
16
  from .data_structures import PeekableQueue
12
17
  from .decimal_parsing import parse_decimal_to_string, parse_string_to_decimal
13
- from .exceptions import OperationalException, ApiException, \
14
- PermissionDeniedApiException, ImproperlyConfigured
18
+ from .exceptions import OperationalException, ApiException, DataError, \
19
+ PermissionDeniedApiException, ImproperlyConfigured, NetworkError
15
20
  from .models import OrderStatus, OrderSide, OrderType, TimeInterval, \
16
- TimeUnit, TimeFrame, TradingTimeFrame, TradingDataType, \
17
- PortfolioConfiguration, Portfolio, Position, Order, TradeStatus, \
18
- BacktestReport, PortfolioSnapshot, StrategyProfile, \
19
- BacktestPosition, Trade, MarketCredential, PositionSnapshot, \
20
- BacktestReportsEvaluation, AppMode, BacktestDateRange
21
- from .services import TickerMarketDataSource, OrderBookMarketDataSource, \
22
- OHLCVMarketDataSource, BacktestMarketDataSource, MarketDataSource, \
23
- MarketService, MarketCredentialService, AbstractPortfolioSyncService, \
24
- RoundingService
25
- from .singleton import Singleton
21
+ TimeUnit, TimeFrame, PortfolioConfiguration, Portfolio, Position, \
22
+ Order, TradeStatus, StrategyProfile, Trade, MarketCredential, \
23
+ AppMode, DataType, DataSource, PortfolioSnapshot, PositionSnapshot, \
24
+ TradeTakeProfit, TradeStopLoss, Event, SnapshotInterval, \
25
+ TakeProfitRule, StopLossRule, PositionSize
26
+ from .order_executor import OrderExecutor
27
+ from .portfolio_provider import PortfolioProvider
28
+ from .services import MarketCredentialService, AbstractPortfolioSyncService, \
29
+ RoundingService, StateHandler
26
30
  from .stateless_actions import StatelessActions
27
31
  from .strategy import Strategy
28
32
  from .utils import random_string, append_dict_as_row_to_csv, \
29
33
  add_column_headers_to_csv, get_total_amount_of_rows, \
30
- load_backtest_report, \
31
- csv_to_list, StoppableThread, pretty_print_backtest_reports_evaluation, \
32
- pretty_print_backtest, load_csv_into_dict, load_backtest_reports
33
- from .graphs import create_prices_graph, create_ema_graph, create_rsi_graph
34
- from .metrics import get_price_efficiency_ratio
34
+ convert_polars_to_pandas, random_number, is_jupyter_notebook, \
35
+ csv_to_list, StoppableThread, load_csv_into_dict, tqdm, \
36
+ is_timezone_aware, sync_timezones, get_timezone
37
+ from .backtesting import BacktestRun, BacktestSummaryMetrics, \
38
+ BacktestDateRange, Backtest, BacktestMetrics, combine_backtests, \
39
+ BacktestPermutationTest, BacktestEvaluationFocus, \
40
+ generate_backtest_summary_metrics
35
41
 
36
42
  __all__ = [
37
- 'Config',
38
43
  "OrderStatus",
39
44
  "OrderSide",
40
45
  "OrderType",
@@ -52,9 +57,6 @@ __all__ = [
52
57
  "DEFAULT_PER_PAGE_VALUE",
53
58
  "DEFAULT_PAGE_VALUE",
54
59
  "SQLALCHEMY_DATABASE_URI",
55
- "TradingDataType",
56
- "TradingTimeFrame",
57
- "Singleton",
58
60
  "random_string",
59
61
  "append_dict_as_row_to_csv",
60
62
  "add_column_headers_to_csv",
@@ -75,49 +77,71 @@ __all__ = [
75
77
  "StatelessActions",
76
78
  "parse_decimal_to_string",
77
79
  "parse_string_to_decimal",
78
- "BacktestReport",
79
- "pretty_print_backtest",
80
+ "BacktestRun",
80
81
  "DATETIME_FORMAT_BACKTESTING",
81
82
  "BACKTESTING_FLAG",
82
- "BACKTESTING_INDEX_DATETIME",
83
83
  "PortfolioSnapshot",
84
84
  "BACKTESTING_START_DATE",
85
85
  "StrategyProfile",
86
- "BacktestPosition",
87
86
  "CCXT_DATETIME_FORMAT",
88
87
  "BACKTEST_DATA_DIRECTORY_NAME",
89
88
  "Trade",
90
89
  "TICKER_DATA_TYPE",
91
90
  "OHLCV_DATA_TYPE",
92
- "TickerMarketDataSource",
93
- "OrderBookMarketDataSource",
94
- "OHLCVMarketDataSource",
95
- "BacktestMarketDataSource",
96
- "MarketDataSource",
97
91
  "CURRENT_UTC_DATETIME",
98
92
  "MarketCredential",
99
- "MarketService",
100
93
  "PeekableQueue",
101
94
  "BACKTESTING_END_DATE",
102
- "BACKTESTING_PENDING_ORDER_CHECK_INTERVAL",
103
95
  "PositionSnapshot",
104
96
  "MarketCredentialService",
105
97
  "TradeStatus",
106
98
  "CCXT_DATETIME_FORMAT_WITH_TIMEZONE",
107
- "pretty_print_backtest_reports_evaluation",
108
- "BacktestReportsEvaluation",
109
99
  "load_csv_into_dict",
110
- "load_backtest_reports",
111
- "SYMBOLS",
112
- "RESERVED_BALANCES",
113
100
  "AbstractPortfolioSyncService",
114
101
  "APP_MODE",
115
102
  "AppMode",
116
103
  "RoundingService",
117
104
  "BacktestDateRange",
118
- "load_backtest_report",
119
- "create_prices_graph",
120
- "create_ema_graph",
121
- "create_rsi_graph",
122
- "get_price_efficiency_ratio"
105
+ "convert_polars_to_pandas",
106
+ "DEFAULT_LOGGING_CONFIG",
107
+ "DATABASE_DIRECTORY_NAME",
108
+ "BACKTESTING_INITIAL_AMOUNT",
109
+ "TradeTakeProfit",
110
+ "TradeStopLoss",
111
+ "StateHandler",
112
+ "APPLICATION_DIRECTORY",
113
+ "DataProvider",
114
+ "NetworkError",
115
+ "DataSource",
116
+ "OrderExecutor",
117
+ "PortfolioProvider",
118
+ "random_number",
119
+ "is_timezone_aware",
120
+ "sync_timezones",
121
+ "get_timezone",
122
+ "Event",
123
+ "SNAPSHOT_INTERVAL",
124
+ "SnapshotInterval",
125
+ "AWS_S3_STATE_BUCKET_NAME",
126
+ "AWS_LAMBDA_LOGGING_CONFIG",
127
+ "DataType",
128
+ "DataSource",
129
+ "Backtest",
130
+ "BacktestMetrics",
131
+ "BacktestSummaryMetrics",
132
+ "BacktestPermutationTest",
133
+ "LAST_SNAPSHOT_DATETIME",
134
+ "DATA_DIRECTORY",
135
+ "INDEX_DATETIME",
136
+ "DATETIME_FORMAT_FILE_NAME",
137
+ "is_jupyter_notebook",
138
+ "tqdm",
139
+ "DEFAULT_DATETIME_FORMAT",
140
+ "BacktestEvaluationFocus",
141
+ 'combine_backtests',
142
+ 'PositionSize',
143
+ 'generate_backtest_summary_metrics',
144
+ 'DataError',
145
+ 'TakeProfitRule',
146
+ 'StopLossRule'
123
147
  ]
@@ -0,0 +1,21 @@
1
+ from .backtest_summary_metrics import BacktestSummaryMetrics
2
+ from .backtest_date_range import BacktestDateRange
3
+ from .backtest_metrics import BacktestMetrics
4
+ from .backtest_run import BacktestRun
5
+ from .backtest import Backtest
6
+ from .backtest_permutation_test import BacktestPermutationTest
7
+ from .backtest_evaluation_focuss import BacktestEvaluationFocus
8
+ from .combine_backtests import combine_backtests, \
9
+ generate_backtest_summary_metrics
10
+
11
+ __all__ = [
12
+ "Backtest",
13
+ "BacktestSummaryMetrics",
14
+ "BacktestDateRange",
15
+ "BacktestMetrics",
16
+ "BacktestRun",
17
+ "BacktestPermutationTest",
18
+ "BacktestEvaluationFocus",
19
+ "combine_backtests",
20
+ "generate_backtest_summary_metrics"
21
+ ]
@@ -0,0 +1,503 @@
1
+ import json
2
+ import os
3
+ from uuid import uuid4
4
+ from pathlib import Path
5
+ from dataclasses import dataclass, field
6
+ from logging import getLogger
7
+ from typing import Dict, Union, List
8
+
9
+ from investing_algorithm_framework.domain.exceptions \
10
+ import OperationalException
11
+
12
+ from .backtest_metrics import BacktestMetrics
13
+ from .backtest_run import BacktestRun
14
+ from .backtest_permutation_test import BacktestPermutationTest
15
+ from .backtest_date_range import BacktestDateRange
16
+ from .backtest_summary_metrics import BacktestSummaryMetrics
17
+ from .combine_backtests import generate_backtest_summary_metrics
18
+
19
+
20
+ logger = getLogger(__name__)
21
+
22
+
23
+ @dataclass
24
+ class Backtest:
25
+ """
26
+ Represents a backtest of an algorithm. It contains the backtest metrics,
27
+ backtest results, and paths to strategy and data files.
28
+
29
+ Attributes:
30
+ backtest_runs (List[BacktestRun]): A list of backtest runs,
31
+ each representing the performance metrics of a single
32
+ backtest run.
33
+ backtest_summary (BacktestSummaryMetrics): An aggregated view of
34
+ the backtest metrics, combining results from multiple backtests
35
+ metrics into a single summary.
36
+ backtest_permutation_tests (List[BacktestPermutationTestMetrics]): A
37
+ list of backtest permutation tests,
38
+ each representing the performance metrics of a single
39
+ backtest permutation test.
40
+ metadata (Dict[str, str]): Metadata related to the backtest, such as
41
+ configuration parameters or additional information about the
42
+ strategy that was backtested. This can be used for later
43
+ reference or analysis.
44
+ risk_free_rate (float): The risk-free rate used in the backtest,
45
+ typically expressed as a decimal (e.g., 0.03 for 3%). This
46
+ strategy_ids (List[int]): List of strategy IDs associated with
47
+ this backtest.
48
+ algorithm_id (int): The ID of the algorithm associated with this
49
+ backtest.
50
+ """
51
+ id: str = field(default_factory=lambda: str(uuid4()))
52
+ backtest_runs: List[BacktestRun] = field(default_factory=list)
53
+ backtest_summary: BacktestSummaryMetrics = field(default=None)
54
+ backtest_permutation_tests: List[BacktestPermutationTest] = \
55
+ field(default_factory=list)
56
+ metadata: Dict[str, str] = field(default_factory=dict)
57
+ risk_free_rate: float = None
58
+ strategy_ids: List[int] = field(default_factory=list)
59
+ algorithm_id: int = None
60
+
61
+ def get_all_backtest_runs(self) -> List[BacktestRun]:
62
+ """
63
+ Retrieve all BacktestRun instances from the backtest.
64
+
65
+ Returns:
66
+ List[BacktestRun]: A list of all BacktestRun instances.
67
+ """
68
+ return self.backtest_runs
69
+
70
+ def get_backtest_run(
71
+ self, date_range: BacktestDateRange
72
+ ) -> Union[BacktestRun, None]:
73
+ """
74
+ Retrieve a specific BacktestRun based on the provided date range.
75
+
76
+ Args:
77
+ date_range (BacktestDateRange): The date range to search for.
78
+
79
+ Returns:
80
+ Union[BacktestRun, None]: The matching BacktestRun if found,
81
+ otherwise None.
82
+ """
83
+ for run in self.backtest_runs:
84
+ if (run.backtest_start_date == date_range.start_date and
85
+ run.backtest_end_date == date_range.end_date):
86
+ return run
87
+ return None
88
+
89
+ def get_all_backtest_permutation_tests(
90
+ self
91
+ ) -> List[BacktestPermutationTest]:
92
+ """
93
+ Retrieve all BacktestPermutationTest instances from the backtest.
94
+
95
+ Returns:
96
+ List[BacktestPermutationTest]: A list of all
97
+ BacktestPermutationTest instances.
98
+ """
99
+ return self.backtest_permutation_tests
100
+
101
+ def get_backtest_permutation_test(
102
+ self, date_range: BacktestDateRange
103
+ ) -> Union[BacktestPermutationTest, None]:
104
+ """
105
+ Retrieve a specific BacktestPermutationTest based on
106
+ the provided date range.
107
+
108
+ Args:
109
+ date_range (BacktestDateRange): The date range to search for.
110
+
111
+ Returns:
112
+ Union[BacktestPermutationTest, None]: The
113
+ matching BacktestPermutationTest if found,
114
+ otherwise None.
115
+ """
116
+ for perm_test in self.backtest_permutation_tests:
117
+ if (perm_test.backtest_start_date == date_range.start_date and
118
+ perm_test.backtest_end_date == date_range.end_date):
119
+ return perm_test
120
+ return None
121
+
122
+ def get_all_backtest_metrics(self) -> List[BacktestMetrics]:
123
+ """
124
+ Retrieve all BacktestMetrics from the backtest runs.
125
+
126
+ Returns:
127
+ List[BacktestMetrics]: A list of BacktestMetrics from
128
+ all backtest runs.
129
+ """
130
+ return [
131
+ run.backtest_metrics for run in self.backtest_runs
132
+ if run.backtest_metrics
133
+ ]
134
+
135
+ def get_backtest_metrics(
136
+ self, date_range: BacktestDateRange
137
+ ) -> Union[BacktestMetrics, None]:
138
+ """
139
+ Retrieve the BacktestMetrics for a specific BacktestRun based on
140
+ the provided date range.
141
+
142
+ Args:
143
+ date_range (Optional[BacktestDateRange]): The date range to
144
+ search for.
145
+
146
+ Returns:
147
+ Union[BacktestMetrics, None]: The BacktestMetrics of the matching
148
+ BacktestRun if found, otherwise None.
149
+ """
150
+ run = self.get_backtest_run(date_range)
151
+ if run:
152
+ return run.backtest_metrics
153
+ return None
154
+
155
+ def to_dict(self) -> dict:
156
+ """
157
+ Convert the Backtest instance to a dictionary.
158
+
159
+ Returns:
160
+ dict: A dictionary representation of the Backtest instance.
161
+ """
162
+
163
+ backtest_summary = self.backtest_summary.to_dict() \
164
+ if self.backtest_summary else None
165
+ return {
166
+ "backtest_runs": [
167
+ br.to_dict() for br in self.backtest_runs
168
+ ] if self.backtest_runs else None,
169
+ "backtest_summary": backtest_summary,
170
+ "backtest_permutation_tests":
171
+ [
172
+ bpt.to_dict() for bpt in self.backtest_permutation_tests
173
+ ] if self.backtest_permutation_tests else None,
174
+ "metadata": self.metadata,
175
+ "risk_free_rate": self.risk_free_rate,
176
+ "strategy_ids": self.strategy_ids,
177
+ "algorithm_id": self.algorithm_id
178
+ }
179
+
180
+ @staticmethod
181
+ def open(
182
+ directory_path: Union[str, Path],
183
+ backtest_date_ranges: List[BacktestDateRange] = None
184
+ ) -> 'Backtest':
185
+ """
186
+ Open a backtest report from a directory and return a Backtest instance.
187
+
188
+ Args:
189
+ directory_path (str): The path to the directory containing the
190
+ backtest report files.
191
+ backtest_date_ranges (List[BacktestDateRange], optional): A list of
192
+ date ranges to filter the backtest runs. If provided, only
193
+ backtest runs matching these date ranges will be loaded.
194
+
195
+ Returns:
196
+ Backtest: An instance of Backtest with the loaded metrics
197
+ and results.
198
+
199
+ Raises:
200
+ OperationalException: If the directory does not exist or if
201
+ there is an error loading the files.
202
+ """
203
+ id = None
204
+ backtest_runs = []
205
+ backtest_summary_metrics = None
206
+ permutation_metrics = []
207
+ metadata = {}
208
+ risk_free_rate = None
209
+
210
+ if not os.path.exists(directory_path):
211
+ raise OperationalException(
212
+ f"The directory {directory_path} does not exist."
213
+ )
214
+
215
+ # Load id if available
216
+ id_file = os.path.join(directory_path, "id.json")
217
+ if os.path.isfile(id_file):
218
+ with open(id_file, 'r') as f:
219
+ try:
220
+ id = json.load(f).get('id', None)
221
+ except json.JSONDecodeError as e:
222
+ logger.error(f"Error decoding id JSON: {e}")
223
+ id = None
224
+
225
+ # Load all backtest runs
226
+ runs_dir = os.path.join(directory_path, "runs")
227
+
228
+ if os.path.isdir(runs_dir):
229
+ for dir_name in os.listdir(runs_dir):
230
+ run_path = os.path.join(runs_dir, dir_name)
231
+ if os.path.isdir(run_path):
232
+
233
+ if backtest_date_ranges is not None:
234
+ temp_run = BacktestRun.open(run_path)
235
+ match_found = False
236
+
237
+ for date_range in backtest_date_ranges:
238
+ if (
239
+ temp_run.backtest_start_date ==
240
+ date_range.start_date and
241
+ temp_run.backtest_end_date ==
242
+ date_range.end_date
243
+ ):
244
+
245
+ if date_range.name is not None:
246
+ if (
247
+ temp_run.backtest_date_range_name ==
248
+ date_range.name
249
+ ):
250
+ match_found = True
251
+ break
252
+ else:
253
+ match_found = True
254
+ break
255
+
256
+ if not match_found:
257
+ continue
258
+
259
+ backtest_runs.append(BacktestRun.open(run_path))
260
+
261
+ # Load combined backtests summary
262
+ if backtest_date_ranges is not None:
263
+ summary_file = os.path.join(directory_path, "summary.json")
264
+
265
+ if os.path.isfile(summary_file):
266
+ backtest_summary_metrics = \
267
+ BacktestSummaryMetrics.open(summary_file)
268
+ else:
269
+ # Generate new summary from loaded backtest runs
270
+ temp_metrics = []
271
+ for br in backtest_runs:
272
+ if br.backtest_metrics:
273
+ temp_metrics.append(br.backtest_metrics)
274
+
275
+ backtest_summary_metrics = \
276
+ generate_backtest_summary_metrics(temp_metrics)
277
+
278
+ # Load backtest permutation test metrics
279
+ perm_test_dir = os.path.join(directory_path, "permutation_tests")
280
+
281
+ if os.path.isdir(perm_test_dir):
282
+ for dir_name in os.listdir(perm_test_dir):
283
+ perm_test_file = os.path.join(perm_test_dir, dir_name)
284
+ if os.path.isdir(perm_test_file):
285
+ permutation_metrics.append(
286
+ BacktestPermutationTest.open(perm_test_file)
287
+ )
288
+
289
+ # Load metadata if available
290
+ meta_file = os.path.join(directory_path, "metadata.json")
291
+
292
+ if os.path.isfile(meta_file):
293
+ with open(meta_file, 'r') as f:
294
+ metadata = json.load(f)
295
+
296
+ # Load risk-free rate if available
297
+ risk_free_rate_file = os.path.join(
298
+ directory_path, "risk_free_rate.json"
299
+ )
300
+
301
+ if os.path.isfile(risk_free_rate_file):
302
+ with open(risk_free_rate_file, 'r') as f:
303
+ try:
304
+ risk_free_rate = json.load(f).get(
305
+ 'risk_free_rate', None
306
+ )
307
+ except json.JSONDecodeError as e:
308
+ logger.error(f"Error decoding risk-free rate JSON: {e}")
309
+ risk_free_rate = None
310
+
311
+ return Backtest(
312
+ id=id,
313
+ backtest_runs=backtest_runs,
314
+ backtest_summary=backtest_summary_metrics,
315
+ backtest_permutation_tests=permutation_metrics,
316
+ metadata=metadata,
317
+ risk_free_rate=risk_free_rate
318
+ )
319
+
320
+ def save(self, directory_path: Union[str, Path]) -> None:
321
+ """
322
+ Save the backtest metrics to a file in JSON format. The metrics will
323
+ always be saved in a file named `metrics.json`
324
+
325
+ Args:
326
+ directory_path (str): The directory where the metrics
327
+ file will be saved.
328
+
329
+ Raises:
330
+ OperationalException: If the directory does not exist or if
331
+ there is an error saving the files.
332
+
333
+ Returns:
334
+ None: This method does not return anything, it saves the
335
+ metrics to a file.
336
+ """
337
+
338
+ if not os.path.exists(directory_path):
339
+ os.makedirs(directory_path)
340
+
341
+ # Save id of the backtest
342
+ id_file = os.path.join(directory_path, "id.json")
343
+ with open(id_file, 'w') as f:
344
+ json.dump({'id': self.id}, f, indent=4)
345
+
346
+ # Call the save method of all backtest runs
347
+ if self.backtest_runs:
348
+ run_path = os.path.join(directory_path, "runs")
349
+ os.makedirs(run_path, exist_ok=True)
350
+
351
+ for br in self.backtest_runs:
352
+ dir_name = br.create_directory_name()
353
+ destination_run_path = os.path.join(run_path, dir_name)
354
+ os.makedirs(destination_run_path, exist_ok=True)
355
+ br.save(destination_run_path)
356
+
357
+ # Save combined backtest metrics if available
358
+ if self.backtest_summary:
359
+ summary_file = os.path.join(
360
+ directory_path, "summary.json"
361
+ )
362
+ self.backtest_summary.save(summary_file)
363
+
364
+ if self.backtest_permutation_tests:
365
+ permutation_dir_path = os.path.join(
366
+ directory_path, "permutation_tests"
367
+ )
368
+ os.makedirs(permutation_dir_path, exist_ok=True)
369
+
370
+ for pm in self.backtest_permutation_tests:
371
+ dir_name = pm.create_directory_name()
372
+ pm_path = os.path.join(permutation_dir_path, dir_name)
373
+ pm.save(pm_path)
374
+
375
+ # Save metadata if available
376
+ if self.metadata:
377
+ meta_file = os.path.join(directory_path, "metadata.json")
378
+ with open(meta_file, 'w') as f:
379
+ json.dump(self.metadata, f, indent=4)
380
+
381
+ # Save risk-free rate if available
382
+ if self.risk_free_rate is not None:
383
+ risk_free_rate_file = os.path.join(
384
+ directory_path, "risk_free_rate.json"
385
+ )
386
+ with open(risk_free_rate_file, 'w') as f:
387
+ json.dump(
388
+ {'risk_free_rate': self.risk_free_rate}, f, indent=4
389
+ )
390
+
391
+ # Save strategy IDs if available
392
+ if self.strategy_ids:
393
+ strategy_ids_file = os.path.join(
394
+ directory_path, "strategy_ids.json"
395
+ )
396
+ with open(strategy_ids_file, 'w') as f:
397
+ json.dump({'strategy_ids': self.strategy_ids}, f, indent=4)
398
+
399
+ # Save algorithm ID if available
400
+ if self.algorithm_id is not None:
401
+ algorithm_id_file = os.path.join(
402
+ directory_path, "algorithm_id.json"
403
+ )
404
+ with open(algorithm_id_file, 'w') as f:
405
+ json.dump(
406
+ {'algorithm_id': self.algorithm_id}, f, indent=4
407
+ )
408
+
409
+ # Save the permutation tests if available
410
+ if self.backtest_permutation_tests:
411
+ permutation_tests_path = os.path.join(
412
+ directory_path, "permutation_tests"
413
+ )
414
+ os.makedirs(permutation_tests_path, exist_ok=True)
415
+
416
+ for bpt in self.backtest_permutation_tests:
417
+ dir_name = bpt.create_directory_name()
418
+ bpt_path = os.path.join(permutation_tests_path, dir_name)
419
+ os.makedirs(bpt_path, exist_ok=True)
420
+ bpt.save(bpt_path)
421
+
422
+ def __repr__(self):
423
+ """
424
+ Return a string representation of the Backtest instance.
425
+
426
+ Returns:
427
+ str: A string representation of the Backtest instance.
428
+ """
429
+ return json.dumps(
430
+ self.to_dict(), indent=4, sort_keys=True, default=str
431
+ )
432
+
433
+ def merge(self, other: 'Backtest') -> 'Backtest':
434
+ """
435
+ Function to merge another Backtest instance into this one.
436
+
437
+ Args:
438
+ other (Backtest): The other Backtest instance to merge.
439
+
440
+ Returns:
441
+ Backtest: The merged Backtest instance.
442
+ """
443
+
444
+ merged = Backtest()
445
+ merged.backtest_runs = self.backtest_runs + other.backtest_runs
446
+
447
+ summary = BacktestSummaryMetrics()
448
+
449
+ for bt_run in merged.get_all_backtest_metrics():
450
+ summary.add(bt_run)
451
+
452
+ merged.backtest_summary = summary
453
+ merged.backtest_permutation_tests = \
454
+ self.backtest_permutation_tests + other.backtest_permutation_tests
455
+
456
+ # Merge metadata
457
+ merged.metadata = {**self.metadata, **other.metadata}
458
+
459
+ if self.risk_free_rate is None:
460
+ merged.risk_free_rate = other.risk_free_rate
461
+
462
+ if self.strategy_ids is None:
463
+ merged.strategy_ids = other.strategy_ids
464
+
465
+ if self.algorithm_id is None:
466
+ merged.algorithm_id = other.algorithm_id
467
+
468
+ return merged
469
+
470
+ def get_backtest_date_ranges(self):
471
+ """
472
+ Get the date ranges for the backtest.
473
+
474
+ Returns:
475
+ List[BacktestDateRange]: A list of BacktestDateRange objects
476
+ representing the date ranges for each backtest run.
477
+ """
478
+ return [
479
+ BacktestDateRange(
480
+ start_date=run.backtest_start_date,
481
+ end_date=run.backtest_end_date,
482
+ name=run.backtest_date_range_name
483
+ )
484
+ for run in self.backtest_runs
485
+ ]
486
+
487
+ def add_permutation_test(
488
+ self, permutation_test: BacktestPermutationTest
489
+ ) -> None:
490
+ """
491
+ Add a permutation test to the backtest.
492
+
493
+ Args:
494
+ permutation_test (BacktestPermutationTest): The permutation test
495
+ to add.
496
+ """
497
+ self.backtest_permutation_tests.append(permutation_test)
498
+
499
+ def __hash__(self):
500
+ return hash(self.id)
501
+
502
+ def __eq__(self, other):
503
+ return isinstance(other, Backtest) and self.id == other.id