Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ These were the major changes contributing to each release:

### 0.x.x

* `Strategy.I()` gained a `warmup=True` parameter. Setting it `False` excludes
that indicator's leading NaN values from the automatic warm-up bar count that
delays the start of the backtest (#1339)

### 0.6.6
(2026-07-22)

Expand Down
3 changes: 2 additions & 1 deletion backtesting/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def _indicator_warmup_nbars(strategy):
return 0
nbars = max((np.isnan(indicator.astype(float)).argmin(axis=-1).max()
for _, indicator in _strategy_indicators(strategy)
if not indicator._opts['scatter']), default=0)
if not indicator._opts['scatter'] and indicator._opts.get('warmup', True)),
default=0)
return nbars


Expand Down
10 changes: 9 additions & 1 deletion backtesting/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def _check_params(self, params):
def I(self, # noqa: E743
func: Callable, *args,
name=None, plot=True, overlay=None, color=None, scatter=False,
warmup=True,
**kwargs) -> np.ndarray:
"""
Declare an indicator. An indicator is just an array of values
Expand Down Expand Up @@ -109,6 +110,13 @@ def I(self, # noqa: E743
If `scatter` is `True`, the plotted indicator marker will be a
circle instead of a connected line segment (default).

If `warmup` is `False`, this indicator's leading NaN values are
_not_ counted towards the number of warm-up bars before which
the backtest doesn't begin trading (see warning below). Set this
for indicators that are informational/secondary only (e.g. computed
on a higher, resampled time frame with `backtesting.lib.resample_apply`)
and aren't essential to have available from the very first bar.

Additional `*args` and `**kwargs` are passed to `func` and can
be used for parameters.

Expand Down Expand Up @@ -175,7 +183,7 @@ def _format_name(name: str) -> str:
overlay = ((x < 1.4) & (x > .6)).mean() > .6

value = _Indicator(value, name=name, plot=plot, overlay=overlay,
color=color, scatter=scatter,
color=color, scatter=scatter, warmup=warmup,
# _Indicator.s Series accessor uses this:
index=self.data.index)
self._indicators.append(value)
Expand Down
14 changes: 14 additions & 0 deletions backtesting/test/_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,20 @@ def coroutine(self):
stats = self._Backtest(coroutine).run()
self.assertEqual(list(stats._trades.Tag), [1, 1, 2])

def test_indicator_warmup_false_excluded_from_start_bar(self):
class S(Strategy):
def init(self):
self.sma_fast = self.I(SMA, self.data.Close, 5)
self.sma_slow = self.I(SMA, self.data.Close, 100, warmup=False)
self.first_next_bar = None

def next(self):
if self.first_next_bar is None:
self.first_next_bar = len(self.data) - 1

stats = Backtest(GOOG, S).run()
self.assertEqual(stats._strategy.first_next_bar, 5)


class TestOptimize(TestCase):
def test_optimize(self):
Expand Down