Skip to content

Commit f26d98d

Browse files
committed
Support multiple vector backtests
1 parent d259af2 commit f26d98d

File tree

6 files changed

+408
-11
lines changed

6 files changed

+408
-11
lines changed

investing_algorithm_framework/app/app.py

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@ def initialize_data_sources(
381381
None
382382
"""
383383
logger.info("Initializing data sources")
384+
385+
if data_sources is None or len(data_sources) == 0:
386+
return
387+
384388
data_provider_service = self.container.data_provider_service()
385389
data_provider_service.reset()
386390

@@ -415,6 +419,10 @@ def initialize_data_sources_backtest(
415419
None
416420
"""
417421
logger.info("Initializing data sources for backtest")
422+
423+
if data_sources is None or len(data_sources) == 0:
424+
return
425+
418426
data_provider_service = self.container.data_provider_service()
419427
data_provider_service.reset()
420428

@@ -900,18 +908,90 @@ def run_backtest(
900908

901909
return backtest
902910

911+
def run_vector_backtests(
912+
self,
913+
backtest_date_range: BacktestDateRange,
914+
initial_amount,
915+
strategies: List[TradingStrategy],
916+
snapshot_interval: SnapshotInterval = SnapshotInterval.DAILY,
917+
risk_free_rate: Optional[float] = None,
918+
skip_data_sources_initialization: bool = False
919+
):
920+
"""
921+
Run vectorized backtests for a set of strategies. The provided
922+
set of strategies need to have their 'buy_signal_vectorized' and
923+
'sell_signal_vectorized' methods implemented to support vectorized
924+
backtesting.
925+
926+
Args:
927+
backtest_date_range: The date range to run the backtest for
928+
(instance of BacktestDateRange)
929+
initial_amount: The initial amount to start the backtest with.
930+
This will be the amount of trading currency that the backtest
931+
portfolio will start with.
932+
strategies (List[TradingStrategy]): List of strategy objects
933+
that need to be backtested. Each strategy should implement
934+
the 'buy_signal_vectorized' and 'sell_signal_vectorized'
935+
methods to support vectorized backtesting.
936+
snapshot_interval (SnapshotInterval): The snapshot
937+
interval to use for the backtest. This is used to determine
938+
how often the portfolio snapshot should be taken during the
939+
backtest. The default is TRADE_CLOSE, which means that the
940+
portfolio snapshot will be taken at the end of each trade.
941+
risk_free_rate (Optional[float]): The risk-free rate to use for
942+
the backtest. This is used to calculate the Sharpe ratio
943+
and other performance metrics. If not provided, the default
944+
risk-free rate will be tried to be fetched from the
945+
US Treasury website.
946+
skip_data_sources_initialization (bool): Whether to skip the
947+
initialization of data sources. This is useful when the data
948+
sources are already initialized, and you want to skip the
949+
initialization step. This will speed up the backtesting
950+
process, but make sure that the data sources are already
951+
initialized before calling this method.
952+
953+
Returns:
954+
List[Backtest]: List of Backtest instances for each strategy
955+
that was backtested.
956+
"""
957+
backtests = []
958+
data_sources = []
959+
960+
for strategy in strategies:
961+
data_sources.extend(strategy.data_sources)
962+
963+
if not skip_data_sources_initialization:
964+
self.initialize_data_sources_backtest(
965+
data_sources, backtest_date_range
966+
)
967+
968+
for strategy in tqdm(strategies):
969+
backtests.append(
970+
self.run_vector_backtest(
971+
backtest_date_range=backtest_date_range,
972+
initial_amount=initial_amount,
973+
strategy=strategy,
974+
snapshot_interval=snapshot_interval,
975+
risk_free_rate=risk_free_rate,
976+
skip_data_sources_initialization=True
977+
)
978+
)
979+
980+
return backtests
981+
903982
def run_vector_backtest(
904983
self,
905984
backtest_date_range: BacktestDateRange,
906985
initial_amount,
907-
strategy,
986+
strategy: TradingStrategy,
908987
snapshot_interval: SnapshotInterval = SnapshotInterval.DAILY,
909988
metadata: Optional[Dict[str, str]] = None,
910989
risk_free_rate: Optional[float] = None,
990+
skip_data_sources_initialization: bool = False
911991
) -> Backtest:
912992
"""
913-
Run a vectorized backtest for an algorithm. The provided algorithm
914-
or set of strategies need to have their 'buy_signal_vectorized' and
993+
Run vectorized backtests for a strategy. The provided
994+
strategy needs to have its 'buy_signal_vectorized' and
915995
'sell_signal_vectorized' methods implemented to support vectorized
916996
backtesting.
917997
@@ -937,6 +1017,12 @@ def run_vector_backtest(
9371017
backtest report. This can be used to store additional
9381018
information about the backtest, such as the author, version,
9391019
parameters or any other relevant information.
1020+
skip_data_sources_initialization (bool): Whether to skip the
1021+
initialization of data sources. This is useful when the data
1022+
sources are already initialized, and you want to skip the
1023+
initialization step. This will speed up the backtesting
1024+
process, but make sure that the data sources are already
1025+
initialized before calling this method.
9401026
9411027
Returns:
9421028
Backtest: Instance of Backtest
@@ -947,9 +1033,11 @@ def run_vector_backtest(
9471033
snapshot_interval=snapshot_interval,
9481034
initial_amount=initial_amount
9491035
)
950-
self.initialize_data_sources_backtest(
951-
strategy.data_sources, backtest_date_range
952-
)
1036+
1037+
if not skip_data_sources_initialization:
1038+
self.initialize_data_sources_backtest(
1039+
strategy.data_sources, backtest_date_range
1040+
)
9531041

9541042
if risk_free_rate is None:
9551043
logger.info("No risk free rate provided, retrieving it...")
@@ -971,7 +1059,17 @@ def run_vector_backtest(
9711059
initial_amount=initial_amount,
9721060
risk_free_rate=risk_free_rate
9731061
)
974-
backtest.metadata = metadata if metadata is not None else {}
1062+
1063+
# Add the metadata to the backtest
1064+
if metadata is None:
1065+
1066+
if strategy.metadata is not None:
1067+
backtest.metadata = {}
1068+
else:
1069+
backtest.metadata = strategy.metadata
1070+
else:
1071+
backtest.metadata = metadata
1072+
9751073
return backtest
9761074

9771075
def run_backtests(

investing_algorithm_framework/app/strategy.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,28 @@ class TradingStrategy:
2727
sources to use for the strategy. The data sources will be used
2828
to indentify data providers that will be called to gather data
2929
and pass to the strategy before its run.
30+
metadata (optional): Dict[str, Any] - a dictionary
31+
containing metadata about the strategy. This can be used to
32+
store additional information about the strategy, such as its
33+
author, version, description, params etc.
3034
"""
31-
time_unit: str = None
35+
time_unit: TimeUnit = None
3236
interval: int = None
3337
worker_id: str = None
3438
strategy_id: str = None
3539
decorated = None
3640
data_sources: List[DataSource] = None
3741
traces = None
3842
context: Context = None
43+
metadata: Dict[str, Any] = None
3944

4045
def __init__(
4146
self,
4247
strategy_id=None,
4348
time_unit=None,
4449
interval=None,
4550
data_sources=None,
51+
metadata=None,
4652
worker_id=None,
4753
decorated=None
4854
):
@@ -65,6 +71,8 @@ def __init__(
6571
if data_sources is not None:
6672
self.data_sources = data_sources
6773

74+
self.metadata = metadata if metadata is not None else {}
75+
6876
if decorated is not None:
6977
self.decorated = decorated
7078

tests/resources/backtest_report/results.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@
3434
"total_value": 1200
3535
}
3636
],
37-
"created_at": "2025-08-21 06:16:46"
37+
"created_at": "2025-08-22 08:08:54"
3838
}

tests/resources/backtest_reports_for_testing/test_algorithm_backtest/results.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@
3434
"total_value": 1000.0
3535
}
3636
],
37-
"created_at": "2025-08-21 06:16:37"
37+
"created_at": "2025-08-22 08:08:44"
3838
}

0 commit comments

Comments
 (0)