◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / backtrader Line Indexing Explained: [0], [-1], and the [1] Bug
Python

backtrader Line Indexing Explained: [0], [-1], and the [1] Bug

In backtrader, a line object indexes time relative to the bar currently being processed: [0] is now, [-1] is the previous bar, and a positive index like [1] reaches into the future — the classic look-ahead footgun. This article explains the line/series model precisely, gives a reference table, and shows the correct patterns for referencing past values. It is educational material about backtest correctness, not financial advice.

What does [0] mean on a backtrader line?

In backtrader, indexing a line is relative to the bar currently being evaluated, not absolute like a normal Python list. Inside your strategy's next() method, self.data.close[0] is the close of the bar being processed right now, self.data.close[-1] is the previous bar's close, self.data.close[-2] is the one before that, and so on. Positive indices point forward in time — into bars that, during live trading, would not exist yet.

This is the single most important thing to internalize: backtrader inverts the intuition many people carry over from pandas or plain lists, where [-1] means "last element" and [1] means "second element." A backtrader line is a moving window anchored to the present bar, and it slides forward as the engine steps through history one bar at a time. [0] is always "here and now," negative offsets are the past, positive offsets are the future.

Because the engine replays history bar by bar, the future genuinely is unknown at each step — unless you reach for it with a positive index. Doing so pulls in information your strategy could not possibly have had live, which is the definition of look-ahead bias. The rest of this article makes the model precise and shows the safe patterns.

The line/series model, precisely

A backtrader line (like close, open, high, low, volume, or the output of an indicator) is a homogeneous series of values indexed by bar. What makes it different from a plain array is the moving anchor. The framework maintains an internal pointer to the current bar; every access is offset from that pointer.

Concretely, at the moment next() runs for bar number N in the dataset:

  • line[0] returns the value at bar N (the current bar).
  • line[-1] returns bar N-1.
  • line[-k] returns bar N-k, for any positive k, as long as N-k >= 0.
  • line[+k] returns bar N+k — a bar the engine has not "reached" in live terms.

The same offset convention applies to indicators. If sma = bt.ind.SMA(self.data.close, period=20), then sma[0] is the moving average as of the current bar and sma[-1] is its value one bar ago. Indicators also expose a warmup concern: sma[0] is only meaningful once 20 bars exist, which is why backtrader delays calling next() until the minimum period is satisfied.

One more subtlety: len(self.data) grows as the engine advances, and self.data.close.get(size=n) returns the last n values as a normal array. Those helpers are safe because they only look backward. The danger is exclusively in positive integer indexing on a line.

Why is [1] the classic look-ahead footgun?

self.data.close[1] asks for the close of the next bar. During a backtest the whole price history is already loaded in memory, so backtrader can and will hand you that future value without error. Nothing crashes. The backtest simply runs with information the strategy could never have had in real time, and the results look far better than reality — a textbook case of [look-ahead bias](/learn/look-ahead-bias).

Here is the trap in the wrong form:

# ❌ WRONG: close[1] is the NEXT bar — you are peeking at the future.
def next(self):
    if self.data.close[1] > self.data.close[0]:  # look-ahead!
        self.buy()

This "buys when the next bar closes higher" — a rule that is trivially profitable and completely unimplementable, because at the current bar you do not yet know the next bar's close. The fix is to only ever reference the current and past bars:

# ✅ CORRECT: compare the current bar to the previous bar.
def next(self):
    if self.data.close[0] > self.data.close[-1]:
        self.buy()

The corrected version keys off close[0] (now) and close[-1] (the prior bar) — both known at decision time. If you genuinely want to act on a breakout confirmed by the current bar, you still act with data available at the current bar; the order fills on the next bar because backtrader executes orders on the following bar's open by default. That built-in next-bar execution is your friend: it means you do not need positive indexing to model realistic delay.

Reference table: backtrader line indices

Keep this table next to you when reading or writing a strategy. It assumes you are inside next() and the engine is processing bar N.

| Index | Bar referenced | Time | Safe to use? | |-------|----------------|------|--------------| | line[0] | N | Current bar (now) | Yes | | line[-1] | N-1 | Previous bar | Yes | | line[-2] | N-2 | Two bars ago | Yes | | line[-k] | N-k | k bars ago | Yes (if N-k >= 0) | | line[1] | N+1 | Next bar | No — look-ahead | | line[2] | N+2 | Two bars ahead | No — look-ahead | | line[+k] | N+k | k bars ahead | No — look-ahead |

Supporting helpers, all backward-looking and safe:

  • line.get(ago=0, size=n) — returns the last n values ending at the current bar as an array.
  • len(self.data) — number of bars processed so far; grows over the run.
  • Indicator lines (sma[0], rsi[-1], etc.) follow the exact same offset rules.

The rule reduces to one sentence: a non-negative-magnitude negative index or zero is the past or present and is fine; any strictly positive index is the future and is a bug. There is essentially no legitimate reason to positively index a data line inside signal logic. If you find one in a codebase, treat it as a defect until proven otherwise.

Catching the bug in review and in tooling

Positive line indexing is insidious because it fails silently — the code runs, the equity curve looks great, and nothing flags an error. That is exactly the profile of the most dangerous backtest bugs, discussed more broadly in [why most backtests fail](/learn/why-most-backtests-fail). Because there is no exception to catch, you have to catch it by discipline and by tooling.

A few habits that help:

  • Grep your strategy for [1], [2], or any [+ on a data or indicator line. Nearly every legitimate access is [0] or negative.
  • Prefer indicator objects over hand-rolled positive-offset math. bt.ind.SMA, bt.ind.Highest, and friends only look backward by construction.
  • Lean on backtrader's default next-bar order execution instead of trying to "time" the future yourself. If you feel the urge to write close[1], what you usually want is next-bar execution, which the broker already provides.
  • Distrust a suspiciously smooth or steep equity curve. Look-ahead bias tends to produce results that are too good, too consistent.

Automated checks catch what human review misses. The free [Python Backtest Validator](/python) scans for look-ahead patterns including positive backtrader line indices, and the [overfitting tool](/backtest/overfitting) helps you probe whether a result is robust or fragile. None of this tells you whether a strategy is profitable — that is not the claim. It tells you whether your backtest is measuring what you think it is measuring, which is the prerequisite for any honest conclusion. This article is educational and is not financial advice.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro