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.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
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,
|
|
9
|
-
CCXT_DATETIME_FORMAT_WITH_TIMEZONE,
|
|
10
|
-
|
|
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,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from .
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
31
|
-
csv_to_list, StoppableThread,
|
|
32
|
-
|
|
33
|
-
from .
|
|
34
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
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
|