From bc6208e14bb968aa2ce90e6737317d9c5763dd2c Mon Sep 17 00:00:00 2001 From: whning0513 <3267069960@qq.com> Date: Mon, 29 Jun 2026 22:50:34 +0800 Subject: [PATCH 1/2] Trim pre-trade flat period from stats --- bt/backtest.py | 16 ++++++++++++++-- tests/test_backtest.py | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/bt/backtest.py b/bt/backtest.py index 8b6317a..f49a936 100644 --- a/bt/backtest.py +++ b/bt/backtest.py @@ -202,6 +202,7 @@ def __init__( self.stats = {} self._original_prices = None + self._stat_prices = None self._weights = None self._sweights = None self.has_run = False @@ -353,8 +354,19 @@ def run(self): if self.progress_bar: bar.stop() - self.stats = self.strategy.prices.calc_perf_stats() self._original_prices = self.strategy.prices + self._stat_prices = self._compute_stat_prices() + self.stats = self._stat_prices.calc_perf_stats() + + def _compute_stat_prices(self): + prices = self.strategy.prices + transactions = self.strategy.get_transactions() + + if not transactions.empty: + first_transaction_date = transactions.index.get_level_values(0)[0] + return prices.loc[first_transaction_date:] + + return prices @property def weights(self): @@ -469,7 +481,7 @@ class Result(ffn.GroupStats): """ def __init__(self, *backtests): - tmp = [pd.DataFrame({x.name: x.strategy.prices}) for x in backtests] + tmp = [pd.DataFrame({x.name: x._stat_prices if x._stat_prices is not None else x.strategy.prices}) for x in backtests] super().__init__(*tmp) self.backtest_list = backtests self.backtests = {x.name: x for x in backtests} diff --git a/tests/test_backtest.py b/tests/test_backtest.py index 1c28a0c..84ef6d7 100644 --- a/tests/test_backtest.py +++ b/tests/test_backtest.py @@ -316,6 +316,28 @@ def test_nested_strategy_backtest_handles_initial_paper_trade_value(): assert result.prices["root"].iloc[0] == 100 +def test_run_after_date_stats_start_on_first_transaction(): + dates = pd.date_range("2000-01-01", "2002-12-31", freq=pd.tseries.offsets.BDay()) + prices = pd.DataFrame(index=dates, data={"a": 100.0}) + prices.loc[dates[260]:, "a"] = np.linspace(100, 150, len(dates[260:])) + + strategy = bt.Strategy( + "delayed", + [ + bt.algos.RunAfterDate("2001-01-01"), + bt.algos.SelectAll(), + bt.algos.WeighEqually(), + bt.algos.Rebalance(), + ], + ) + + result = bt.run(bt.Backtest(strategy, prices, progress_bar=False)) + + first_transaction_date = result.get_transactions().index.get_level_values(0)[0] + assert result.stats["delayed"].start == first_transaction_date + assert result.prices.index[0] == first_transaction_date + + def test_30_min_data(): names = ["foo"] dates = pd.date_range(start="2017-01-01", end="2017-12-31", freq="30min") From 15019a2080caba6c07d3e9f98dbd09e0de0913f6 Mon Sep 17 00:00:00 2001 From: whn <142425816+Whning0513@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:04:39 +0800 Subject: [PATCH 2/2] Align renormalized fixed-income result prices --- bt/backtest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bt/backtest.py b/bt/backtest.py index f49a936..272aedf 100644 --- a/bt/backtest.py +++ b/bt/backtest.py @@ -699,7 +699,12 @@ def __init__(self, normalizing_value, *backtests): raise ValueError(f"Cannot apply RenormalizedFixedIncomeResult because backtest {backtest.name} is not on a fixed income strategy") if not isinstance(normalizing_value, dict): normalizing_value = {x.name: normalizing_value for x in backtests} - tmp = [pd.DataFrame({x.name: self._price(x.strategy, normalizing_value[x.name])}) for x in backtests] + tmp = [] + for backtest in backtests: + prices = self._price(backtest.strategy, normalizing_value[backtest.name]) + if backtest._stat_prices is not None: + prices = prices.reindex(backtest._stat_prices.index) + tmp.append(pd.DataFrame({backtest.name: prices})) super(Result, self).__init__(*tmp) self.backtest_list = backtests self.backtests = {x.name: x for x in backtests}