Skip to content

Commit 2524b13

Browse files
committed
Fix checkpoints logic
1 parent dd719b6 commit 2524b13

File tree

14 files changed

+1651
-942
lines changed

14 files changed

+1651
-942
lines changed

investing_algorithm_framework/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from .app import App, Algorithm, generate_strategy_id, \
22
TradingStrategy, StatelessAction, Task, AppHook, Context, \
3-
add_html_report, BacktestReport, save_backtests_to_directory, \
3+
add_html_report, BacktestReport, \
44
pretty_print_trades, pretty_print_positions, \
55
pretty_print_orders, pretty_print_backtest, select_backtest_date_ranges, \
6-
get_equity_curve_with_drawdown_chart, load_backtests_from_directory, \
6+
get_equity_curve_with_drawdown_chart, \
77
get_rolling_sharpe_ratio_chart, rank_results, \
88
get_monthly_returns_heatmap_chart, create_weights, \
99
get_yearly_returns_bar_chart, get_entry_and_exit_signals, \
@@ -13,12 +13,13 @@
1313
TimeUnit, TimeInterval, Order, Portfolio, Backtest, DataError, \
1414
Position, TimeFrame, INDEX_DATETIME, MarketCredential, TakeProfitRule, \
1515
PortfolioConfiguration, RESOURCE_DIRECTORY, AWS_LAMBDA_LOGGING_CONFIG, \
16-
Trade, APP_MODE, AppMode, DATETIME_FORMAT, \
16+
Trade, APP_MODE, AppMode, DATETIME_FORMAT, load_backtests_from_directory, \
1717
BacktestDateRange, convert_polars_to_pandas, BacktestRun, \
1818
DEFAULT_LOGGING_CONFIG, DataType, DataProvider, StopLossRule, \
1919
TradeStatus, generate_backtest_summary_metrics, \
2020
APPLICATION_DIRECTORY, DataSource, OrderExecutor, PortfolioProvider, \
21-
SnapshotInterval, AWS_S3_STATE_BUCKET_NAME, BacktestEvaluationFocus
21+
SnapshotInterval, AWS_S3_STATE_BUCKET_NAME, BacktestEvaluationFocus, \
22+
save_backtests_to_directory
2223
from .infrastructure import AzureBlobStorageStateHandler, \
2324
CSVOHLCVDataProvider, CCXTOHLCVDataProvider, PandasOHLCVDataProvider, \
2425
AWSS3StorageStateHandler

investing_algorithm_framework/app/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
get_yearly_returns_bar_chart, get_equity_curve_chart, \
1515
get_ohlcv_data_completeness_chart, get_entry_and_exit_signals
1616
from .analysis import select_backtest_date_ranges, rank_results, \
17-
create_weights, load_backtests_from_directory, \
18-
save_backtests_to_directory, generate_strategy_id
17+
create_weights, generate_strategy_id
1918

2019

2120
__all__ = [
@@ -43,7 +42,5 @@
4342
"create_weights",
4443
"get_entry_and_exit_signals",
4544
"get_equity_curve_chart",
46-
"load_backtests_from_directory",
47-
"save_backtests_to_directory",
4845
"generate_strategy_id"
4946
]
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from .backtest_data_ranges import select_backtest_date_ranges
22
from .ranking import rank_results, create_weights, combine_backtest_metrics
33
from .permutation import create_ohlcv_permutation
4-
from .backtest_utils import load_backtests_from_directory, \
5-
save_backtests_to_directory
64
from .strategy_id import generate_strategy_id
75

86
__all__ = [
@@ -11,7 +9,5 @@
119
"create_weights",
1210
"create_ohlcv_permutation",
1311
"combine_backtest_metrics",
14-
"load_backtests_from_directory",
15-
"save_backtests_to_directory",
1612
"generate_strategy_id"
1713
]

investing_algorithm_framework/app/app.py

Lines changed: 53 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import gc
2-
import shutil
31
import inspect
42
import logging
53
import os
@@ -20,11 +18,11 @@
2018
BACKTESTING_START_DATE, BACKTESTING_END_DATE, APP_MODE, MarketCredential, \
2119
AppMode, BacktestDateRange, DATABASE_DIRECTORY_NAME, DataSource, \
2220
BACKTESTING_INITIAL_AMOUNT, SNAPSHOT_INTERVAL, Backtest, DataError, \
23-
PortfolioConfiguration, SnapshotInterval, DataType, combine_backtests, \
21+
PortfolioConfiguration, SnapshotInterval, DataType, \
2422
PortfolioProvider, OrderExecutor, ImproperlyConfigured, TimeFrame, \
2523
DataProvider, INDEX_DATETIME, tqdm, BacktestPermutationTest, \
26-
LAST_SNAPSHOT_DATETIME, BACKTESTING_FLAG, \
27-
generate_backtest_summary_metrics, DATA_DIRECTORY
24+
LAST_SNAPSHOT_DATETIME, BACKTESTING_FLAG, DATA_DIRECTORY, \
25+
generate_backtest_summary_metrics
2826
from investing_algorithm_framework.infrastructure import setup_sqlalchemy, \
2927
create_all_tables, CCXTOrderExecutor, CCXTPortfolioProvider, \
3028
BacktestOrderExecutor, CCXTOHLCVDataProvider, clear_db, \
@@ -34,9 +32,7 @@
3432
DefaultTradeOrderEvaluator, get_risk_free_rate_us
3533
from .app_hook import AppHook
3634
from .eventloop import EventLoopService
37-
from .analysis import create_ohlcv_permutation, save_backtests_to_directory, \
38-
load_backtests_from_directory
39-
35+
from .analysis import create_ohlcv_permutation
4036

4137
logger = logging.getLogger("investing_algorithm_framework")
4238
COLOR_RESET = '\033[0m'
@@ -1021,211 +1017,68 @@ def filter_function(
10211017
List[Backtest]: List of Backtest instances for each strategy
10221018
that was backtested.
10231019
"""
1024-
backtests = []
1025-
algorithm_ids = [strategy.algorithm_id for strategy in strategies]
1026-
algorithm_ids_selection = algorithm_ids.copy()
1027-
data_sources = []
1020+
backtest_service = self.container.backtest_service()
10281021

1029-
# Only used when backtest_storage_directory is None and
1030-
# backtest_date_ranges is provided
1031-
backtests_ordered_by_algorithm = {}
1022+
if use_checkpoints and backtest_storage_directory is None:
1023+
raise OperationalException(
1024+
"backtest_storage_directory must be provided when "
1025+
"use_checkpoints is set to True"
1026+
)
10321027

10331028
if backtest_date_range is None and backtest_date_ranges is None:
10341029
raise OperationalException(
10351030
"Either backtest_date_range or backtest_date_ranges must be "
10361031
"provided"
10371032
)
10381033

1039-
for strategy in strategies:
1040-
data_sources.extend(strategy.data_sources)
1041-
1042-
if risk_free_rate is None:
1043-
logger.info("No risk free rate provided, retrieving it...")
1044-
risk_free_rate = get_risk_free_rate_us()
1045-
1046-
if risk_free_rate is None:
1047-
raise OperationalException(
1048-
"Could not retrieve risk free rate for backtest metrics."
1049-
"Please provide a risk free as an argument when running "
1050-
"your backtest or make sure you have an internet "
1051-
"connection"
1052-
)
1053-
1054-
if backtest_date_range is not None:
1055-
if not skip_data_sources_initialization:
1056-
self.initialize_data_sources_backtest(
1057-
data_sources,
1058-
backtest_date_range,
1059-
show_progress=show_progress
1060-
)
1061-
1062-
for strategy in tqdm(
1063-
strategies, colour="green", desc="Running backtests"
1064-
):
1065-
backtests.append(
1066-
self.run_vector_backtest(
1067-
backtest_date_range=backtest_date_range,
1068-
initial_amount=initial_amount,
1069-
strategy=strategy,
1070-
snapshot_interval=snapshot_interval,
1071-
risk_free_rate=risk_free_rate,
1072-
skip_data_sources_initialization=True,
1073-
market=market,
1074-
trading_symbol=trading_symbol,
1075-
continue_on_error=continue_on_error,
1076-
use_checkpoints=use_checkpoints,
1077-
backtest_storage_directory=backtest_storage_directory,
1078-
show_progress=False,
1079-
)
1080-
)
1081-
1082-
# Apply filter function if set
1083-
if filter_function is not None:
1084-
backtests = filter_function(backtests, backtest_date_range)
1034+
if not skip_data_sources_initialization:
1035+
print("adding default data providers")
1036+
data_provider_service = self.container.data_provider_service()
1037+
data_provider_service.reset()
10851038

1086-
# Save to storage directory if provided
1087-
if backtest_storage_directory is not None:
1088-
save_backtests_to_directory(
1089-
backtests=backtests,
1090-
directory_path=backtest_storage_directory,
1039+
for data_provider_tuple in self._data_providers:
1040+
data_provider_service.add_data_provider(
1041+
data_provider_tuple[0], priority=data_provider_tuple[1]
10911042
)
10921043

1093-
return backtests
1094-
else:
1095-
active_strategies = strategies.copy()
1096-
storage_directories = {}
1097-
1098-
# Make sure that the backtest date ranges are sorted by start date
1099-
# and unique
1100-
unique_date_ranges = set(backtest_date_ranges)
1101-
backtest_date_ranges = sorted(
1102-
unique_date_ranges, key=lambda x: x.start_date
1044+
# Add the default data providers
1045+
data_provider_service.add_data_provider(
1046+
CCXTOHLCVDataProvider()
11031047
)
11041048

1105-
for backtest_date_range in tqdm(
1106-
backtest_date_ranges,
1107-
colour="green",
1108-
desc="Running backtests for all date ranges"
1109-
):
1110-
backtest_results = []
1111-
if not skip_data_sources_initialization:
1112-
self.initialize_data_sources_backtest(
1113-
data_sources,
1114-
backtest_date_range,
1115-
show_progress=show_progress
1116-
)
1117-
1118-
start_date = backtest_date_range\
1119-
.start_date.strftime('%Y-%m-%d')
1120-
end_date = backtest_date_range.end_date.strftime('%Y-%m-%d')
1121-
1122-
if backtest_storage_directory is not None:
1123-
storage_directories[backtest_date_range] = os.path.join(
1124-
backtest_storage_directory,
1125-
f"{start_date}_to_{end_date}"
1126-
)
1127-
1128-
# Run backtests for active strategies only
1129-
for strategy in tqdm(
1130-
active_strategies,
1131-
colour="green",
1132-
desc=f"Running backtests for "
1133-
f"{start_date} to {end_date}"
1134-
):
1135-
backtest_results.append(
1136-
self.run_vector_backtest(
1137-
backtest_date_range=backtest_date_range,
1138-
initial_amount=initial_amount,
1139-
strategy=strategy,
1140-
snapshot_interval=snapshot_interval,
1141-
risk_free_rate=risk_free_rate,
1142-
skip_data_sources_initialization=True,
1143-
market=market,
1144-
trading_symbol=trading_symbol,
1145-
use_checkpoints=use_checkpoints,
1146-
backtest_storage_directory=(
1147-
backtest_storage_directory
1148-
),
1149-
show_progress=False
1150-
)
1151-
)
1152-
1153-
# Apply filter function after each date range to determine
1154-
# which strategies continue to the next period
1155-
if filter_function is not None:
1156-
backtest_results = filter_function(
1157-
backtest_results, backtest_date_range
1158-
)
1159-
algorithm_ids_selection = [
1160-
backtest.algorithm_id for backtest in backtest_results
1161-
]
1162-
active_strategies = [
1163-
strategy for strategy in active_strategies
1164-
if strategy.algorithm_id in algorithm_ids_selection
1165-
]
1166-
1167-
# Save the intermediate backtests to a temp storage location
1168-
# if backtest_storage_directory is provided and clean up memory
1169-
if backtest_storage_directory is not None:
1170-
path = storage_directories[backtest_date_range]
1171-
save_backtests_to_directory(
1172-
backtests=backtest_results, directory_path=path,
1173-
)
1174-
1175-
else:
1176-
for backtest in backtest_results:
1177-
backtests_ordered_by_algorithm.setdefault(
1178-
backtest.algorithm_id, []
1179-
).append(backtest)
1180-
1181-
del backtest_results
1182-
1183-
# Free up memory
1184-
gc.collect()
1185-
1186-
def load_backtest_filter_fn(bt: Backtest) -> bool:
1187-
return bt.algorithm_id in algorithm_ids_selection
1188-
1189-
# load all backtests from storage directories and combine them
1190-
if backtest_storage_directory is not None:
1191-
for backtest_range in storage_directories:
1192-
path = storage_directories[backtest_range]
1193-
loaded_backtests = load_backtests_from_directory(
1194-
directory_path=path,
1195-
filter_function=load_backtest_filter_fn
1196-
)
1197-
1198-
for backtest in loaded_backtests:
1199-
backtests_ordered_by_algorithm.setdefault(
1200-
backtest.algorithm_id, []
1201-
).append(backtest)
1202-
1203-
# Remove all temp storage directories
1204-
shutil.rmtree(path)
1205-
else:
1206-
# Remove all strategies that are not in the final selection
1207-
backtests_ordered_by_algorithm = {
1208-
algorithm_id: backtests
1209-
for algorithm_id, backtests in
1210-
backtests_ordered_by_algorithm.items()
1211-
if algorithm_id in algorithm_ids_selection
1212-
}
1213-
1214-
for algorith_id in backtests_ordered_by_algorithm.keys():
1215-
backtests.append(
1216-
combine_backtests(
1217-
backtests_ordered_by_algorithm[algorith_id]
1218-
)
1219-
)
1220-
1221-
if backtest_storage_directory is not None:
1222-
# Save final combined backtests to storage directory
1223-
save_backtests_to_directory(
1224-
backtests=backtests,
1225-
directory_path=backtest_storage_directory,
1226-
)
1227-
1228-
return backtests
1049+
if use_checkpoints:
1050+
sdsi = skip_data_sources_initialization
1051+
return backtest_service.run_vector_backtests_with_checkpoints(
1052+
strategies=strategies,
1053+
backtest_date_range=backtest_date_range,
1054+
backtest_date_ranges=backtest_date_ranges,
1055+
snapshot_interval=snapshot_interval,
1056+
risk_free_rate=risk_free_rate,
1057+
initial_amount=initial_amount,
1058+
skip_data_sources_initialization=sdsi,
1059+
show_progress=show_progress,
1060+
market=market,
1061+
trading_symbol=trading_symbol,
1062+
continue_on_error=continue_on_error,
1063+
backtest_storage_directory=backtest_storage_directory,
1064+
filter_function=filter_function
1065+
)
1066+
sdsi = skip_data_sources_initialization
1067+
return backtest_service.run_vector_backtests(
1068+
strategies=strategies,
1069+
backtest_date_range=backtest_date_range,
1070+
backtest_date_ranges=backtest_date_ranges,
1071+
snapshot_interval=snapshot_interval,
1072+
risk_free_rate=risk_free_rate,
1073+
initial_amount=initial_amount,
1074+
skip_data_sources_initialization=sdsi,
1075+
show_progress=show_progress,
1076+
market=market,
1077+
trading_symbol=trading_symbol,
1078+
continue_on_error=continue_on_error,
1079+
filter_function=filter_function,
1080+
backtest_storage_directory=backtest_storage_directory,
1081+
)
12291082

12301083
def run_vector_backtest(
12311084
self,

investing_algorithm_framework/dependency_container.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from investing_algorithm_framework.app.algorithm import AlgorithmFactory
44
from investing_algorithm_framework.app.context import Context
55
from investing_algorithm_framework.infrastructure import SQLOrderRepository, \
6-
SQLPositionRepository, SQLPortfolioRepository, \
6+
SQLPositionRepository, SQLPortfolioRepository, BacktestService, \
77
SQLPortfolioSnapshotRepository, SQLTradeRepository, \
88
SQLPositionSnapshotRepository, SQLTradeStopLossRepository, \
99
SQLTradeTakeProfitRepository, SQLOrderMetadataRepository
1010
from investing_algorithm_framework.services import OrderService, \
1111
PositionService, PortfolioService, PortfolioConfigurationService, \
12-
BacktestService, ConfigurationService, PortfolioSnapshotService, \
12+
ConfigurationService, PortfolioSnapshotService, \
1313
PositionSnapshotService, MarketCredentialService, TradeService, \
1414
PortfolioSyncService, OrderExecutorLookup, PortfolioProviderLookup, \
1515
DataProviderService, TradeTakeProfitService, TradeStopLossService

investing_algorithm_framework/domain/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
from .backtesting import BacktestRun, BacktestSummaryMetrics, \
3838
BacktestDateRange, Backtest, BacktestMetrics, combine_backtests, \
3939
BacktestPermutationTest, BacktestEvaluationFocus, \
40-
generate_backtest_summary_metrics
40+
generate_backtest_summary_metrics, load_backtests_from_directory, \
41+
save_backtests_to_directory
4142

4243
__all__ = [
4344
"OrderStatus",
@@ -143,5 +144,7 @@
143144
'generate_backtest_summary_metrics',
144145
'DataError',
145146
'TakeProfitRule',
146-
'StopLossRule'
147+
'StopLossRule',
148+
"load_backtests_from_directory",
149+
"save_backtests_to_directory",
147150
]

0 commit comments

Comments
 (0)