Skip to content

Commit ff2858b

Browse files
committed
Add support for final_filter_function
1 parent 7d55e3f commit ff2858b

File tree

2 files changed

+106
-25
lines changed

2 files changed

+106
-25
lines changed

investing_algorithm_framework/app/app.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -926,9 +926,12 @@ def run_vector_backtests(
926926
market: Optional[str] = None,
927927
trading_symbol: Optional[str] = None,
928928
continue_on_error: bool = False,
929-
filter_function: Optional[
929+
window_filter_function: Optional[
930930
Callable[[List[Backtest], BacktestDateRange], List[Backtest]]
931931
] = None,
932+
final_filter_function: Optional[
933+
Callable[[List[Backtest]], List[Backtest]]
934+
] = None,
932935
backtest_storage_directory: Optional[Union[str, Path]] = None,
933936
use_checkpoints: bool = False
934937
) -> List[Backtest]:
@@ -982,7 +985,7 @@ def run_vector_backtests(
982985
backtests if an error occurs in one of the backtests. If set
983986
to True, the backtest will return an empty Backtest instance
984987
in case of an error. If set to False, the error will be raised.
985-
filter_function (
988+
window_filter_function (
986989
Optional[Callable[[List[Backtest], BacktestDateRange],
987990
List[Backtest]]]
988991
):
@@ -1000,6 +1003,20 @@ def filter_function(
10001003
backtests: List[Backtest],
10011004
backtest_date_range: BacktestDateRange
10021005
) -> List[Backtest]
1006+
final_filter_function (
1007+
Optional[Callable[[List[Backtest]], List[Backtest]]]
1008+
):
1009+
A function that takes a list of Backtest objects and
1010+
returns a filtered list of Backtest objects. This is applied
1011+
after all backtest date ranges have been processed when
1012+
backtest_date_ranges is provided. Only the strategies from
1013+
the filtered backtests will be returned as the final result.
1014+
This allows for final filtering of strategies based on
1015+
their overall performance across all periods. The function
1016+
signature should be:
1017+
def filter_function(
1018+
backtests: List[Backtest]
1019+
) -> List[Backtest]
10031020
backtest_storage_directory (Optional[Union[str, Path]]): If
10041021
provided, the backtests will be saved to the
10051022
specified directory after each backtest is completed.
@@ -1061,7 +1078,8 @@ def filter_function(
10611078
trading_symbol=trading_symbol,
10621079
continue_on_error=continue_on_error,
10631080
backtest_storage_directory=backtest_storage_directory,
1064-
filter_function=filter_function
1081+
window_filter_function=window_filter_function,
1082+
final_filter_function=final_filter_function,
10651083
)
10661084
sdsi = skip_data_sources_initialization
10671085
return backtest_service.run_vector_backtests(
@@ -1076,7 +1094,8 @@ def filter_function(
10761094
market=market,
10771095
trading_symbol=trading_symbol,
10781096
continue_on_error=continue_on_error,
1079-
filter_function=filter_function,
1097+
window_filter_function=window_filter_function,
1098+
final_filter_function=final_filter_function,
10801099
backtest_storage_directory=backtest_storage_directory,
10811100
)
10821101

investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -844,9 +844,12 @@ def run_vector_backtests_with_checkpoints(
844844
market: Optional[str] = None,
845845
trading_symbol: Optional[str] = None,
846846
continue_on_error: bool = False,
847-
filter_function: Optional[
847+
window_filter_function: Optional[
848848
Callable[[List[Backtest], BacktestDateRange], List[Backtest]]
849849
] = None,
850+
final_filter_function: Optional[
851+
Callable[[List[Backtest]], List[Backtest]]
852+
] = None,
850853
backtest_storage_directory: Optional[Union[str, Path]] = None,
851854
):
852855
"""
@@ -863,7 +866,7 @@ def run_vector_backtests_with_checkpoints(
863866
initialization of data sources.
864867
show_progress (bool): Whether to show a progress bar and
865868
debug for the different processing steps.
866-
filter_function (
869+
window_filter_function (
867870
Optional[Callable[[List[Backtest], BacktestDateRange],
868871
List[Backtest]]]
869872
):
@@ -881,6 +884,20 @@ def filter_function(
881884
backtests: List[Backtest],
882885
backtest_date_range: BacktestDateRange
883886
) -> List[Backtest]
887+
final_filter_function (
888+
Optional[Callable[[List[Backtest]], List[Backtest]]]
889+
):
890+
A function that takes a list of Backtest objects and
891+
returns a filtered list of Backtest objects. This is applied
892+
after all backtest date ranges have been processed when
893+
backtest_date_ranges is provided. Only the strategies from
894+
the filtered backtests will be returned as the final result.
895+
This allows for final filtering of strategies based on
896+
their overall performance across all periods. The function
897+
signature should be:
898+
def filter_function(
899+
backtests: List[Backtest]
900+
) -> List[Backtest]
884901
Returns:
885902
None
886903
"""
@@ -954,7 +971,9 @@ def filter_function(
954971
missing_backtests = []
955972

956973
for strategy in tqdm(
957-
strategies, colour="green", desc="Running backtests"
974+
strategies,
975+
colour="green",
976+
desc=f"{GREEN_COLOR}Running backtests{COLOR_RESET}"
958977
):
959978
missing_backtests.append(
960979
self.run_vector_backtest(
@@ -974,9 +993,15 @@ def filter_function(
974993

975994
backtests.extend(missing_backtests)
976995

977-
# Apply filter function if set
978-
if filter_function is not None:
979-
backtests = filter_function(backtests, backtest_date_range)
996+
# Apply window filter function if set
997+
if window_filter_function is not None:
998+
backtests = window_filter_function(
999+
backtests, backtest_date_range
1000+
)
1001+
1002+
# Apply final filter function if set
1003+
if final_filter_function is not None:
1004+
backtests = final_filter_function(backtests)
9801005

9811006
backtests_to_be_saved = [
9821007
bt for bt in missing_backtests if bt in backtests
@@ -1012,7 +1037,8 @@ def filter_function(
10121037
for backtest_date_range in tqdm(
10131038
backtest_date_ranges,
10141039
colour="green",
1015-
desc="Running backtests for all date ranges"
1040+
desc=f"{GREEN_COLOR}Running backtests for "
1041+
f"all date ranges{COLOR_RESET}"
10161042
):
10171043
if not skip_data_sources_initialization:
10181044
self.initialize_data_sources_backtest(
@@ -1055,7 +1081,7 @@ def filter_function(
10551081
else:
10561082
print(
10571083
GREEN_COLOR +
1058-
f"Found {len(backtest_results)} checkpoints, " +
1084+
f"Found {len(backtest_results)} checkpoints." +
10591085
COLOR_RESET
10601086
)
10611087

@@ -1064,8 +1090,9 @@ def filter_function(
10641090
for strategy in tqdm(
10651091
strategies_to_run,
10661092
colour="green",
1067-
desc=f"Running backtests for {start_date} "
1068-
f"to {end_date}"
1093+
desc=f"{GREEN_COLOR}Running backtests "
1094+
f"for {start_date} "
1095+
f"to {end_date}{COLOR_RESET}"
10691096
):
10701097
backtest_results.append(
10711098
self.run_vector_backtest(
@@ -1086,8 +1113,8 @@ def filter_function(
10861113

10871114
# Apply filter function after each date range to determine
10881115
# which strategies continue to the next period
1089-
if filter_function is not None:
1090-
backtest_results = filter_function(
1116+
if window_filter_function is not None:
1117+
backtest_results = window_filter_function(
10911118
backtest_results, backtest_date_range
10921119
)
10931120
active_algorithm_ids = [
@@ -1128,6 +1155,10 @@ def filter_function(
11281155
)
11291156
)
11301157

1158+
# Apply final filter function if set
1159+
if final_filter_function is not None:
1160+
backtests = final_filter_function(backtests)
1161+
11311162
if show_progress:
11321163
print(GREEN_COLOR + "Saving all backtests ..." + COLOR_RESET)
11331164

@@ -1159,9 +1190,12 @@ def run_vector_backtests(
11591190
market: Optional[str] = None,
11601191
trading_symbol: Optional[str] = None,
11611192
continue_on_error: bool = False,
1162-
filter_function: Optional[
1193+
window_filter_function: Optional[
11631194
Callable[[List[Backtest], BacktestDateRange], List[Backtest]]
11641195
] = None,
1196+
final_filter_function: Optional[
1197+
Callable[[List[Backtest]], List[Backtest]]
1198+
] = None,
11651199
backtest_storage_directory: Optional[Union[str, Path]] = None,
11661200
):
11671201
"""
@@ -1178,7 +1212,7 @@ def run_vector_backtests(
11781212
initialization of data sources.
11791213
show_progress (bool): Whether to show a progress bar and
11801214
debug for the different processing steps.
1181-
filter_function (
1215+
window_filter_function (
11821216
Optional[Callable[[List[Backtest], BacktestDateRange],
11831217
List[Backtest]]]
11841218
):
@@ -1196,6 +1230,20 @@ def filter_function(
11961230
backtests: List[Backtest],
11971231
backtest_date_range: BacktestDateRange
11981232
) -> List[Backtest]
1233+
final_filter_function (
1234+
Optional[Callable[[List[Backtest]], List[Backtest]]]
1235+
):
1236+
A function that takes a list of Backtest objects and
1237+
returns a filtered list of Backtest objects. This is applied
1238+
after all backtest date ranges have been processed when
1239+
backtest_date_ranges is provided. Only the strategies from
1240+
the filtered backtests will be returned as the final result.
1241+
This allows for final filtering of strategies based on
1242+
their overall performance across all periods. The function
1243+
signature should be:
1244+
def filter_function(
1245+
backtests: List[Backtest]
1246+
) -> List[Backtest]
11991247
Returns:
12001248
None
12011249
"""
@@ -1240,7 +1288,9 @@ def filter_function(
12401288
backtests = []
12411289

12421290
for strategy in tqdm(
1243-
strategies, colour="green", desc="Running backtests"
1291+
strategies,
1292+
colour="green",
1293+
desc=f"{GREEN_COLOR}Running backtests{COLOR_RESET}"
12441294
):
12451295
backtests.append(
12461296
self.run_vector_backtest(
@@ -1259,8 +1309,14 @@ def filter_function(
12591309
)
12601310

12611311
# Apply filter function if set
1262-
if filter_function is not None:
1263-
backtests = filter_function(backtests, backtest_date_range)
1312+
if window_filter_function is not None:
1313+
backtests = window_filter_function(
1314+
backtests, backtest_date_range
1315+
)
1316+
1317+
# Apply final filter function if set
1318+
if final_filter_function is not None:
1319+
backtests = final_filter_function(backtests)
12641320

12651321
if show_progress:
12661322
print(
@@ -1293,7 +1349,8 @@ def filter_function(
12931349
for backtest_date_range in tqdm(
12941350
backtest_date_ranges,
12951351
colour="green",
1296-
desc="Running backtests for all date ranges"
1352+
desc=f"{GREEN_COLOR}Running backtests "
1353+
f"for all date ranges{COLOR_RESET}"
12971354
):
12981355

12991356
if not skip_data_sources_initialization:
@@ -1312,7 +1369,8 @@ def filter_function(
13121369
for strategy in tqdm(
13131370
strategies,
13141371
colour="green",
1315-
desc=f"Running backtests for {start_date} to {end_date}"
1372+
desc=f"{GREEN_COLOR}Running backtests "
1373+
f"for {start_date} to {end_date}{COLOR_RESET}"
13161374
):
13171375
backtest_results.append(
13181376
self.run_vector_backtest(
@@ -1330,8 +1388,8 @@ def filter_function(
13301388

13311389
# Apply filter function after each date range to determine
13321390
# which strategies continue to the next period
1333-
if filter_function is not None:
1334-
backtest_results = filter_function(
1391+
if window_filter_function is not None:
1392+
backtest_results = window_filter_function(
13351393
backtest_results, backtest_date_range
13361394
)
13371395
active_algorithm_ids = [
@@ -1372,6 +1430,10 @@ def filter_function(
13721430
)
13731431
)
13741432

1433+
# Apply final filter function if set
1434+
if final_filter_function is not None:
1435+
backtests = final_filter_function(backtests)
1436+
13751437
if backtest_storage_directory is not None:
13761438
if show_progress:
13771439
print(

0 commit comments

Comments
 (0)