Algotradium

Backtesting · Python Tutorial backtesting.py data columns

Backtesting.py Data: Open, High, Low, Close & Volume Explained

Backtesting.py data columns explained: Open, High, Low, Close and Volume

In this guide, you'll learn the exact DataFrame structure Backtesting.py expects: backtesting.py data columns, required OHLCV fields, case sensitivity rules, how to rename columns, how to prepare yfinance data, and the most common data errors that break a backtest before it starts.

Backtesting.py DataFrame Columns: Quick Answer

ColumnRequiredDescriptionExample
OpenYesOpening price100.5
HighYesHighest price103.2
LowYesLowest price99.8
CloseYesClosing price102.4
VolumeOptionalTrading volume152340

Column names are case sensitive: Open works, open does not. A DatetimeIndex is recommended for time-series data, although Backtesting.py can also work with a monotonic RangeIndex.


Introduction

So you've decided to give Backtesting.py a shot. Good choice. It's a lightweight Python library that lets you test trading strategies against historical price data without a ton of ceremony. But before you can run your first backtest, you have to feed it data. And that's where a lot of people trip up.

The library expects a pandas DataFrame with a specific structure. Not just any data will do. You might have a CSV full of prices, or a DataFrame pulled from Yahoo Finance, but if the columns aren't named exactly right, Backtesting.py will throw an error before you even get to see a single trade.

The short version: you need Open, High, Low, and Close. Volume is optional. A DatetimeIndex is recommended, but not strictly mandatory if your index is monotonic. That's the whole game.

🔑 The core idea

Backtesting.py asks your DataFrame one simple question: "Are you a clean, chronological OHLCV table?" If the answer is yes — with correctly named columns and a suitable index — you're ready to run. If not, you'll be debugging before you ever see a drawdown curve.

By the end of this guide, you'll know exactly what to plug into Backtest(): the right columns, the right capitalization, the right index, and how to handle messy real-world data. We'll walk through preparing a DataFrame from scratch, loading data from Yahoo Finance, and troubleshooting the most common errors.

If you'd rather see a complete working example before you build your own, our hands-on tutorial on how to backtest a trading strategy in Python with backtesting.py walks through the whole flow with real price data. For a broader foundation, the complete guide to Python backtesting covers data collection and strategy development in more detail.

Let's get that data in shape.

What Data Format Does Backtesting.py Require?

Why the DataFrame shape matters more than your strategy

Here's the thing: Backtesting.py is opinionated. It won't accept a Python list of dictionaries, a NumPy array, or a raw CSV file as your data source. You need a pandas DataFrame with the right column names and a suitable index. Get this right and the library does all the heavy lifting. Get it wrong and you'll be staring at a cryptic KeyError before your strategy even gets a chance to run.

The good news? Once you understand the pattern, it becomes second nature. Whether you're pulling data from Yahoo Finance, your broker, or a CSV you saved years ago, the preparation steps are the same.

📋 Backtesting.py Data Requirements at a Glance
  • Type: pandas DataFrame
  • Index: A DatetimeIndex is recommended for time-series data, although Backtesting.py can also work with a monotonic RangeIndex
  • Required columns: Open, High, Low, Close
  • Optional column: Volume, when volume data is needed by your strategy or indicators
  • Data types: Price and volume data should be numeric
  • Missing values: Backtesting.py does not accept missing OHLC values, so remove or appropriately fill incomplete rows before running the backtest
  • Column names: Use the exact column names expected by Backtesting.py; open is different from Open

The Index: Your DataFrame's Backbone

Why does the index matter this much? Because Backtesting.py has to iterate over time. It needs to know which candle comes first and which comes second. For most financial datasets, a DatetimeIndex is the best choice because it preserves the actual timing of each bar and makes the resulting backtest easier to inspect. However, Backtesting.py can also work with a monotonic RangeIndex.

For time-series data, a sorted and appropriately indexed DataFrame is recommended. A DatetimeIndex is usually the best choice for market data. If you have duplicates or out-of-order dates, you may get strange results, so it's worth cleaning them up before running a backtest.

Here's a quick example of what a valid DataFrame looks like:

import pandas as pd

df = pd.DataFrame({
    "Open":  [100.0, 101.5, 102.0],
    "High":  [103.2, 104.8, 105.1],
    "Low":   [99.4, 100.1, 101.3],
    "Close": [102.7, 104.2, 103.9],
    "Volume": [150000, 175000, 168000]
}, index=pd.to_datetime(["2024-01-02", "2024-01-03", "2024-01-04"]))
print(df)
A view of the resulting DataFrame showing the DatetimeIndex and the Open, High, Low, Close and Volume columns
Every row is one complete candle, and the dates are the index — not a column.

See what's going on here? The index holds the trading dates, and every row is one complete candle. The date is not a column in the data — it IS the index. That distinction causes a lot of confusion, especially if you're used to working with CSV files where the date sits in the first column.

Column Names: Exact, Capitalized, No Excuses

Backtesting.py looks for column names with machine-like precision. It checks if "Open" exists, "High", "Low", and "Close". Technically these are the only four required. If you include "Volume", it has to be exactly that — no "volume", no "Vol", no abbreviation. And yes, they're case-sensitive. "OPEN" won't work, no matter how stylistically beautiful it looks.

Notice how all the price columns hold floats. You can use integers if your data is clean, but floats are more common in real-world market data. What you absolutely cannot have are strings, timestamps, or any object dtype hiding inside those columns. If your "Close" column contains something like "1,023.50" with a comma and a dollar sign, pandas won't complain — but Backtesting.py will.

Missing Values: The Silent Strategy Killer

NaN is your enemy here. Backtesting.py doesn't fill forward, interpolate, or magically heal broken bars. It expects a clean, gap-free dataset where every row is a complete candle. If a single row has a missing High or Close, that bar is unusable. Your backtest might crash, or worse, produce results that look fine but are subtly wrong.

Backtesting.py does not accept missing OHLC values, so you need to remove or appropriately fill incomplete rows before running the backtest. A targeted approach is usually best:

df = df.dropna(subset=["Open", "High", "Low", "Close"])
# If your strategy also needs Volume:
# df = df.dropna(subset=["Open", "High", "Low", "Close", "Volume"])
⚠️ Don't get clever with ffilling if you don't understand why data is missing. A stale Close can create phantom signals. It's usually safer to drop missing bars entirely — especially if you're working with daily data where a one-day gap won't kill your strategy.

Data Types: Floats, Integers, and Everything Else

Let's talk about dtypes for a moment. In practice, the prices from your broker or from yfinance arrive as floats. Volume often arrives as an integer. That's totally fine — Backtesting.py accepts both. But if you download data from a source that stores prices as strings (you know who you are), you'll need to convert them:

for col in ["Open", "High", "Low", "Close", "Volume"]:
    df[col] = pd.to_numeric(df[col], errors="coerce")

df = df.dropna(subset=["Open", "High", "Low", "Close"])

The errors="coerce" argument is a lifesaver. It turns anything non-numeric into NaN, and then you drop those rows. Simple, but effective.

Real-World Example: Pulling Data from Yahoo Finance

Using yfinance? Here's a gotcha: the downloaded DataFrame often comes with a MultiIndex on the columns, especially if you request multiple tickers. Backtesting.py expects a flat column index, so you need to flatten it first. Here's the cleanest way to prepare it:

import yfinance as yf
import pandas as pd

df = yf.download("AAPL", start="2023-01-01", end="2024-01-01")

if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

df.index = pd.to_datetime(df.index)
df = df[["Open", "High", "Low", "Close", "Volume"]]
df = df.dropna(subset=["Open", "High", "Low", "Close"])
df.head()

The isinstance(df.columns, pd.MultiIndex) check is important: if the columns are already flat, you don't need to do anything. Only flatten when a MultiIndex is actually present.

What About the Date Column in Your CSV?

If you're loading CSV data, you'll likely have a column named Date. When loading CSV data, it is usually best to explicitly parse the date column and set it as the index rather than relying on implicit conversion:

df = pd.read_csv("your_data.csv")
df["Date"] = pd.to_datetime(df["Date"])
df = df.set_index("Date")
df = df.sort_index()
df = df[["Open", "High", "Low", "Close", "Volume"]]
df = df.dropna(subset=["Open", "High", "Low", "Close"])

Make sure your DataFrame is ordered chronologically before running the backtest. Sorting the index with df.sort_index() helps ensure that price data is processed in the correct time sequence and can prevent unexpected backtesting results caused by incorrectly ordered data.

💡 Pro tip: Always validate your DataFrame shape before running a backtest with print(df.columns.tolist()) and print(df.index). Spending a few seconds on this can save you minutes of debugging.

You can see that same pattern in action in this hands-on Backtesting.py tutorial, where we walk through preparing yfinance data for a live strategy. And if you're newer to the whole pipeline, the complete Python backtesting guide covers data collection, cleaning, and strategy development in one place.

So, to recap: one row per time period, a proper index, and OHLCV as exact, numeric columns. That's the format. Once your DataFrame fits that shape, you can stop wrestling with the library and actually start testing ideas. Which, let's be honest, is the part you actually care about.

Backtesting.py DataFrame Columns

Let's zoom in on the actual column names Backtesting.py expects. This is where a lot of first-time users trip up and end up staring at a KeyError that makes zero sense. The library is strict. It's not configurable. You can't call your opening price column 'first_trade' or your close column 'close' and expect it to just magically work. Here's the exact structure you need to feed it.

ColumnRequired?What it holdsExample value
OpenYesOpening price of the period100.5
HighYesHighest price reached103.2
LowYesLowest price reached99.8
CloseYesClosing price of the period102.4
VolumeOptionalNumber of shares or contracts152340

Notice that Volume is the only optional one. If your strategy checks volume signals, by all means include it. But if you don't have volume data, Backtesting.py won't complain. What about Open, High, Low, Close? They're all mandatory. No exceptions. And the names have to be capitalized exactly like that. A common mistake is using lowercase, or something like 'close' instead of 'Close'. That'll trigger a ValueError.

Additional Columns Are Allowed

Backtesting.py requires the standard OHLC columns, but your DataFrame can also contain additional columns that your strategy uses, such as sentiment scores or custom indicators. The official documentation explicitly says the DataFrame can have extra columns, and these columns can be accessed by your Strategy. So don't feel like you have to strip everything down to exactly five columns — just make sure the required OHLC columns are present and correctly named.

Diagram showing a pandas DataFrame with the required columns Open, High, Low, Close, Volume and a datetime index
A clean OHLCV DataFrame: the four required columns plus the optional Volume on a datetime index.

Why capitalization matters

Backtesting.py looks for those exact strings when it initializes the Backtest object. It does something like data['Open'] internally. If your dataframe has 'open' instead, Python raises a KeyError. And if you're missing a column entirely, you'll get a message like ValueError: Some columns are missing from DataFrames: Open, High, Low, Close.

So how do you check before running a backtest? Easy:

# Quick sanity check before creating a Backtest
print(df.columns.tolist())
# Expected output: ['Open', 'High', 'Low', 'Close', 'Volume']

If you see lowercase or odd names, just rename them with pandas:

import pandas as pd

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})

# Confirm the fix
print(df.columns.tolist())

A quick comparison: common column names vs. what Backtesting.py needs

If you're pulling data from a CSV export or an API like Alpha Vantage, you might encounter any of these variations:

Source column nameBacktesting.py requiredWhat happens?
openOpenKeyError
Close PriceCloseKeyError
O, H, L, COpen, High, Low, CloseMissing column error
VolVolumeIgnored or error depending on strategy

What about the index and dtypes?

Columns aren't the only thing you need to get right. A sorted and appropriately indexed DataFrame is recommended. The columns should also be numeric. If you load data from a CSV and forget to parse dates, you might end up with object columns, which is a pain. The good news is fixing this is straightforward once you know what to look for.

# Ensure index is datetime and sorted
df.index = pd.to_datetime(df.index)
df.sort_index(inplace=True)

# Drop rows with missing OHLC values
df = df.dropna(subset=["Open", "High", "Low", "Close"])

# Coerce to numeric if needed
df = df.apply(pd.to_numeric, errors="coerce")
df = df.dropna(subset=["Open", "High", "Low", "Close"])

A classic gotcha: MultiIndex columns from yfinance

If you download data with yfinance, you might get back a MultiIndex on the columns. That means df.columns looks like [('Open', 'AAPL'), ('High', 'AAPL'), ...]. Backtesting.py expects a flat index, so you need to flatten it before passing it in:

import yfinance as yf
import pandas as pd

df = yf.download('AAPL', start='2020-01-01', end='2021-01-01')

# Flatten MultiIndex columns if present
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.dropna(subset=["Open", "High", "Low", "Close"])

Now you're ready to run your backtest. If you want to see this working end-to-end with an actual strategy, our step-by-step Python backtesting tutorial walks you through the entire pipeline, from raw yfinance data to a plotted backtest. And if you're curious about the broader data preparation and strategy development process, the complete Python backtesting guide covers cleaning, indicator engineering, and performance pitfalls in more detail.

Remember this

Backtesting.py expects specific column names: Open, High, Low, Close, and optionally Volume. Make sure your DataFrame uses these names exactly before running a backtest to avoid common data-related errors.

So, next time you're debugging a backtest that won't start, don't blame your strategy right away. Check your dataframe. Check the column names. Check the index. In many cases, it's just a formatting problem — and now you know exactly how to solve it.

Are Backtesting.py Column Names Case Sensitive?

Yes, completely case sensitive. And I mean brutally case sensitive. Backtesting.py looks for the exact strings 'Open', 'High', 'Low', 'Close', and 'Volume'. If your DataFrame has 'open' or 'OPEN', it doesn't match. Same for 'VOL' or 'volume'.

You might think you're being clever by using lowercase or a different abbreviation. But the library doesn't care. It will throw an error like:

KeyError: "Column 'Open' does not exist"

Wait, that error message doesn't even list the actual column name? Right, that's part of the frustration. You check your DataFrame, you see a column called open, and you wonder why Backtesting.py can't find it. This is one of the quickest ways to get stuck on a simple backtest.

The Fix: Rename Like a Pro

The fix is trivial, though. Use pandas rename() to standardise the names before feeding your data to the backtester:

import pandas as pd

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})

# Verify your column names
print(df.columns.tolist())

That print() line is worth its weight in gold. It saves you from the silent disaster of thinking your columns are right when they're not.

Visual diagram showing how a lowercase column named 'open' fails while a renamed 'Open' column succeeds in Backtesting.py
Capitalization is enforced: 'open' fails, 'Open' succeeds.

A Quick Comparison Table

Backtesting.py expectsWhat you might haveResult
Openopen❌ Error
HighHIGH❌ Error
Lowlow❌ Error
CloseClose Price❌ Error
VolumeVolOptional, but if present it must match

The Sneaky Whitespace Problem

Here's another trap: extra whitespace. A column named ' Open ' — with spaces on either side — will also fail. It's easy to overlook when you're importing data from an Excel file or a CSV with messy headers. So after renaming, it's worth stripping any whitespace from column names too:

df.columns = df.columns.str.strip()

That one-liner has saved me more times than I care to admit. Combine it with the rename step and you'll have a clean DataFrame that Backtesting.py accepts without complaint.

If you're new to preparing data for backtesting, you might be interested in the broader steps in our Python Backtesting: The Complete Guide. And if you want to see the exact yfinance workflow from pandas DataFrame to a running backtest, the Backtesting.py tutorial walks through it all.

Pro tip: Always run df.columns.tolist() immediately after loading your data. Look at the actual strings — not what you think they are. This single habit will eliminate most of the "column not found" errors people hit with Backtesting.py.

So, to sum it up: yes, Backtesting.py is strict about capitalization. But the fix is simple. Rename, strip whitespace, verify, and move on. The earlier you internalise this, the less time you'll burn debugging something that should take seconds.

How to Prepare a Pandas DataFrame for Backtesting.py

Preparing your DataFrame isn't the glamorous part of backtesting, but honestly, it's where most bugs are born. Maybe you've already tried to run a backtest and hit something like KeyError: 'Open' or ValueError: DatetimeIndex expected. That's normal. Let's walk through a simple, repeatable routine that takes about two minutes and saves you a whole lot of debugging.

Step 1: Make Sure Your Index Is Suitable

A DatetimeIndex is usually the best choice for market data. If your data has a column called Timestamp or Date, set it as the index and convert it in one go:

df['Date'] = pd.to_datetime(df['Date'])
df.set_index('Date', inplace=True)

If the index already exists but the values are strings, convert it directly:

df.index = pd.to_datetime(df.index)

Here's a subtle one. Your index should be sorted in ascending order. Trading data out of order? It happens more than you'd think, especially if you're stitching together multiple CSV files. Fix it with a single line:

df.sort_index(inplace=True)

Step 2: Validate Your Column Names

Backtesting.py is picky about column names. It wants exactly Open, High, Low, Close, and optionally Volume. Not open. Not OPEN. Not Adj Close.

You can check what you're working with in one line:

print(df.columns.tolist())

Step 3: Rename Columns — Carefully

If your columns are close but not exact, use .rename(). This is the safer route because you're mapping names explicitly:

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})

You might be tempted to do the shortcut I see everywhere:

df.columns = ['Open', 'High', 'Low', 'Close', 'Volume']

That works — but only if the column order is exactly correct. If your CSV has extra columns like Adj Close before volume, you just introduced a subtle bug that might not crash immediately but will quietly ruin your results. If you're not sure about the order, rename individually. Trust me, being explicit is worth it.

Quick check after renaming

Run df.columns.tolist() again. You should see exactly ['Open', 'High', 'Low', 'Close', 'Volume'] or at least the first four of those.

Step 4: Handle Missing Values

Backtesting.py does not accept missing OHLC values, so you need to remove or appropriately fill incomplete rows before running the backtest:

df = df.dropna(subset=["Open", "High", "Low", "Close"])
# If your strategy also needs Volume:
# df = df.dropna(subset=["Open", "High", "Low", "Close", "Volume"])

This is better than a blanket df.dropna() because it avoids dropping rows with valid OHLC data just because an additional column (like sentiment) happens to be NaN.

Step 5: Check for Duplicate Timestamps

Duplicate datetime indexes can sneak in when you download data across multiple time periods and combine them. To detect duplicates:

if df.index.duplicated().any():
    print('Duplicates found! Fixing...')
    df = df[~df.index.duplicated()]

Step 6: Verify Before You Backtest

Before passing anything to Backtest(), run a final sanity check:

df.info()
df.head()

Look for the right columns, no missing values in OHLC, and a suitably ordered index. That's it — no need to overthink this.

TaskCodeWhy It Matters
Convert indexdf.index = pd.to_datetime(df.index)Makes time-based slicing easier
Sort indexdf.sort_index(inplace=True)Prevents wrong order during strategy simulation
Rename columnsdf.rename(columns=...)Correct capitalization is mandatory
Drop missing OHLC valuesdf.dropna(subset=["Open","High","Low","Close"])Avoids errors and silent bugs from incomplete bars
Remove duplicatesdf[~df.index.duplicated()]Ensures unique timestamps

My personal prep routine

Every time I pull data from a new source, I write those six checks before I even look at a chart. It might feel like extra work, but it absolutely prevents the classic "my strategy looks great but it's actually just a data bug" moment.

Once your DataFrame is clean, you're ready to feed it into Backtest(). If you want to see this exact preparation in action with a real Yahoo Finance download, check out How to Backtest a Trading Strategy in Python with backtesting.py or the broader Python Backtesting: The Complete Guide (2026) for context on where this fits in the bigger picture.

Take it from someone who has debugged too many ruined backtests: a few lines of cleaning now will save you hours of scratching your head later. Your future self will thank you.

How to Rename DataFrame Columns for Backtesting.py

So you've got price data sitting in a DataFrame, but the columns look anything but tidy. Maybe they're lowercase. Maybe they came from a CSV with names like open_price, high_price, close_price, and vol. Or perhaps you downloaded crypto data with columns named o, h, l, c, v.

Here's the thing: backtesting.py is strict. Not mean-strict, just precise-strict. It needs to see the exact capitalized strings Open, High, Low, Close, and Volume. No shortcuts. No aliases. Nope, not even OPEN with all caps.

The exact rename pattern is refreshingly simple. Start with whichever names you have and map them to the right ones:

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})

Wait, is that it? Yes. That's the whole core of it. But let me show you a couple of real-world variations, because the columns you have are rarely all lowercase.

Common source mappings

Here are some real-world examples of how different data sources name their columns and what you need to map them to:

df = df.rename(columns={
    'date': 'Date',
    'o': 'Open',
    'h': 'High',
    'l': 'Low',
    'c': 'Close',
    'v': 'Volume'
})

If your data uses Date or datetime as the date column, you might leave it alone — just make sure it becomes the DataFrame index later. But the OHLCV columns absolutely must match the expected names.

Column name mapping cheat sheet

You have
open
Backtesting.py wants
Open
Fast fix
'open': 'Open'
You have
o, h, l, c, v
Backtesting.py wants
Open, High, Low, Close, Volume
Fast fix
'o':'Open', 'h':'High', ...
Common sourceTypical column namesRename action
Yahoo Finance (yfinance)Open, High, Low, Close, VolumeUsually no rename needed, just flatten MultiIndex
Alpha Vantage1. open, 2. high, 3. low, 4. close, 5. volumeStrip numbers and dots, then map
Binance APIopen_time, open, high, low, close, volumeRename open/high/low/close/volume, handle timestamp
CSV exportsOpenPrice, HighPrice, LowPrice, ClosePrice, VolMap each to the canonical short names

What about extra columns?

Backtesting.py doesn't mind extra columns sitting in your DataFrame. In fact, the official documentation says the DataFrame can contain additional columns that your strategy can use. That's why a lot of people follow the rename with a safety cut:

df = df[['Open', 'High', 'Low', 'Close', 'Volume']]

That keeps only the columns backtesting.py cares about, which usually feels more peaceful anyway. Warning: only do this after you've converted your date column into the index. Otherwise you'll slice it right out of the DataFrame.

Always verify, because typos are real

After renaming, run this tiny print statement:

print(df.columns.tolist())

If you see ['Open', 'High', 'Low', 'Close', 'Volume'] you're good to go. If you see anything else, squint and fix the typo before moving on. I cannot tell you how many backtesting.py errors I've debugged that just came down to a lowercase v or an extra underscore lurking in the column list.

Before and after column names shown in a terminal after running df.columns.tolist()
Always inspect your columns before feeding them into Backtest().

Ready to actually run the backtest? If you're loading from Yahoo Finance, the rename step often pairs with a MultiIndex flatten — exactly what I cover in How to Backtest a Trading Strategy in Python with backtesting.py. And if you want the full-fledged workflow from CSV to results, the Python Backtesting: The Complete Guide walks through the data prep step by step. But honestly, once you've got the columns right and verified them, the hardest part is behind you. Backtesting.py will take it from there.

Golden rule: after any rename, always run df.columns.tolist() and eyeball it. That one line saves more hours than any fancy validator I know.

How to Load Yahoo Finance Data into Backtesting.py

Yahoo Finance is a convenient source of historical market data for Python backtesting. The yfinance library can download OHLCV data directly into a pandas DataFrame, but the downloaded data may need some preparation before it is passed to Backtesting.py.

Using yfinance to Download OHLCV Data

The yfinance library provides a simple way to download historical price data:

import yfinance as yf

df = yf.download('AAPL', start='2020-01-01', end='2023-12-31')

Depending on the yfinance options and version you are using, the returned DataFrame may contain a MultiIndex for its columns. Instead of assuming a particular column structure, check for a MultiIndex and flatten it when necessary:

if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

After this step, the price columns can be accessed using their standard names, such as Open, High, Low, Close, and Volume.

Flatten the MultiIndex and clean the data

Backtesting.py works with a pandas DataFrame containing the standard OHLC price columns: Open, High, Low, and Close. Volume is optional and can be retained when your strategy or indicators use volume data.

Example yfinance columnColumn used by Backtesting.py
('Open', 'AAPL')Open
('High', 'AAPL')High
('Low', 'AAPL')Low
('Close', 'AAPL')Close
('Volume', 'AAPL')Volume

Additional columns may also be present in the DataFrame. If they are not needed by your strategy, you can select only the columns required for your backtest:

df = df[['Open', 'High', 'Low', 'Close', 'Volume']]

It is also a good practice to check for missing values before running the backtest. Backtesting.py does not accept missing OHLC values, so remove or appropriately fill incomplete rows:

df = df.dropna(subset=["Open", "High", "Low", "Close"])

Heads-up: Make sure your DataFrame uses a suitable time-based index and that the rows are in chronological order. If necessary, convert the index with df.index = pd.to_datetime(df.index) and sort it with df = df.sort_index().

Slice it and run

Once the DataFrame has been checked and prepared, select the columns needed by your backtest:

df = df[['Open', 'High', 'Low', 'Close', 'Volume']]

The complete preparation workflow can look like this:

import yfinance as yf
import pandas as pd
from backtesting import Backtest

df = yf.download('AAPL', start='2020-01-01', end='2023-12-31')

# Flatten MultiIndex columns when necessary
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

# Keep the columns needed by the backtest
df = df[['Open', 'High', 'Low', 'Close', 'Volume']]

# Clean and prepare the data
df = df.dropna(subset=["Open", "High", "Low", "Close"])
df.index = pd.to_datetime(df.index)
df = df.sort_index()

# Now run your Backtest
bt = Backtest(df, YourStrategy)
results = bt.run()
Quick checklist
1. Check for a MultiIndex
2. Flatten it only when necessary
3. Select the required OHLC columns
4. Retain Volume if your strategy needs it
5. Handle missing OHLC values
6. Confirm a chronological time-based index
7. Run your backtest
Common yfinance gotcha
When yfinance returns MultiIndex columns, expressions such as df[('Open','AAPL')] refer to the hierarchical column structure rather than the standard Open column. Flattening the MultiIndex when necessary makes the DataFrame easier to work with and aligns the column names with the format expected by Backtesting.py.

If you're pulling data from a CSV instead, the same basic preparation principles apply: check the column names, data types, missing values, and chronological order before running the backtest. For a fuller walkthrough of building strategies with Backtesting.py, see our practical Backtesting.py tutorial or the broader complete Python backtesting guide.

Preparing the DataFrame before running a backtest helps prevent common data-format problems and makes the results easier to interpret. A few simple validation steps can save considerable debugging time later.

Common Backtesting.py Data Errors

You've written your strategy, cleaned your data, and hit run. Then boom — a wall of red text. If you've spent any time on trading forums or Stack Overflow, you've probably seen the same desperate questions: "Why won't Backtesting.py find my Open column?" or "What does 'DatetimeIndex is not unique' even mean?"

Here's the thing: Backtesting.py is picky. Not in a malicious way — more like a meticulous librarian who needs everything in exactly the right spot before handing you a book. The good news? Nearly every data error boils down to one of a handful of issues, and all of them are quick fixes once you know the diagnosis.

1. Missing Columns: The KeyError Wall

This is the most common error, hands down. The library looks for exact strings: Open, High, Low, Close. If your DataFrame doesn't have one of those, you get a KeyError and your backtest dies instantly. Sometimes you accidentally filtered a column out during cleaning. Other times your data source named things differently — OPEN, price_open, Open Price, whatever. The fix is a simple rename:

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'vol': 'Volume'
})

After renaming, always verify with print(df.columns.tolist()). You'd be surprised how many "mysterious" errors vanish once you actually see what your columns are called.

2. Wrong Column Capitalization

Yes, column names are case sensitive. Open and open are completely different strings to the library. If your data source uses lowercase headers, you'll get the exact same missing-column error as above. The rename trick handles this too. Just remember: Backtesting.py expects title case — Open, High, Low, Close, and optionally Volume. No exceptions, no abbreviations.

3. Datetime Index Problems

Another classic. A sorted and appropriately indexed DataFrame is recommended for time-series backtesting. If your index is a RangeIndex or a bunch of strings, you may still be able to run a backtest, but the results are harder to interpret. For time-series data, a DatetimeIndex is usually the best choice because it preserves the actual timing of each bar. If you have duplicate dates from a bad merge or timezone mishap, you may see unexpected results — which honestly sounds terrifying until you realize it just means you've got repeats. Trust me, you don't want the confusing route. It wastes hours.

# Convert index to datetime
df.index = pd.to_datetime(df.index)

# Remove duplicates, keep the first occurrence
df = df[~df.index.duplicated(keep='first')]

# Sort ascending — recommended for chronological order
df = df.sort_index()

# Confirm it worked
print(type(df.index))  # <class 'pandas.core.indexes.datetimes.DatetimeIndex'>

4. NaN Values: The Silent Saboteur

Missing data is sneaky. Sometimes the backtest runs fine but gives you wildly unrealistic results. Other times it crashes with a confusing error deep inside the library — and you blame your strategy when it was really just a few empty cells. yfinance especially loves returning NaN for non-trading days or halted stocks. Backtesting.py does not accept missing OHLC values, so a targeted drop is usually best:

df = df.dropna(subset=["Open", "High", "Low", "Close"])
# If your strategy also needs Volume:
# df = df.dropna(subset=["Open", "High", "Low", "Close", "Volume"])

Want to see the damage before dropping? Run print(df.isna().sum()). That shows you exactly how many missing values live in each column. If you're losing lots of rows, you might want to investigate why rather than blindly deleting data. A halted stock for three weeks shouldn't be silently averaged into your backtest.

5. The MultiIndex Columns Gotcha from yfinance

This one has become increasingly common. When you download data with yf.download('AAPL', ...), the resulting DataFrame can have a MultiIndex on columns — tuples like ('Open', 'AAPL'). The column isn't literally named Open anymore; it's a tuple. Backtesting.py throws its hands up and says "I can't find Open!" because, well, it's looking for a string and finding a tuple. The fix is to flatten the columns only when a MultiIndex is present:

df = yf.download('AAPL', start='2024-01-01', end='2025-01-01')

if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

That strips the ticker level and leaves clean Open, High, Low, Close, Volume columns. If you want a fully walked-through example of this exact workflow, check out our step-by-step backtesting tutorial.

Quick Reference: Symptom → Fix

ProblemCauseFix
Open missingWrong or missing columnRename to Open
High missingWrong or missing columnRename to High
Low missingWrong or missing columnRename to Low
Close missingWrong or missing columnRename to Close
lowercase columnsCase mismatchRename columns
NaN OHLCIncomplete barsdf.dropna(subset=["Open","High","Low","Close"])
MultiIndexyfinance outputFlatten when isinstance(df.columns, pd.MultiIndex)
Wrong indexUnparsed datesdf.index = pd.to_datetime(df.index)
Shortcut: if anything looks fishy, just print your DataFrame. print(df.head()) and print(df.columns) reveal most data issues in about five seconds. The error message gives you a clue, but the DataFrame tells you the truth.

One More Thing: Volume Is Optional (But Smart)

Backtesting.py only strictly requires Open, High, Low, and Close. Volume can be optional depending on your strategy. But if you do include it, it must be named Volume — again, case sensitive. And if your strategy references self.data.Volume, make sure the column survives your cleaning pipeline. It's heartbreaking to lose Volume during a dropna() because an empty row was in the middle of your dataset.

The bigger lesson here? Data errors are actually your friend. They surface the exact line that broke, and they force you to look at your DataFrame with fresh eyes. Once you've hit each of these once, you'll never trip over them again. And if you want to step back and see data prep from the full 30,000-foot view — including collection, cleaning, and why all this matters — our complete Python backtesting guide covers it end to end.

When in doubt: print, rename, handle NaNs, sort. You'll be back to testing strategies in no time.

Backtesting.py Data Example

You've got the theory down, but theory doesn't fill your brokerage account. Let's put it all together with a real example. Suppose you want to backtest a simple moving average crossover strategy on Apple data. Here's the full code — no fluff, just the stuff that works:

import pandas as pd
import yfinance as yf
from backtesting import Backtest, Strategy

# 1. Load data
df = yf.download("AAPL", start="2020-01-01", end="2022-01-01")

# 2. Flatten MultiIndex columns if present
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

# 3. Prepare the DataFrame
df.index = pd.to_datetime(df.index)
df = df.sort_index()
df = df[["Open", "High", "Low", "Close", "Volume"]]
df = df.dropna(subset=["Open", "High", "Low", "Close"])

# 4. Define a simple strategy
class MyStrategy(Strategy):
    def init(self):
        pass

    def next(self):
        pass

# 5. Run the backtest
bt = Backtest(df, MyStrategy, cash=10_000, commission=.001)
results = bt.run()
print(results)

The strategy itself is outside the scope of this article; the important part here is the DataFrame passed to Backtest(). Notice how the cleaning steps are ordered: flatten the columns, convert and sort the index, select the OHLCV columns, then drop rows with missing OHLC values. Do it in the wrong order, and you're hunting for bugs in your data pipeline instead of testing strategies.

📥 Grab the data

Yahoo Finance via yfinance is the easiest starting point. Just specify a ticker and date range and you're most of the way there.

🧹 Make it backtesting.py-ready

Flatten the columns when needed, fix the index, select exactly Open, High, Low, Close, Volume, and handle missing OHLC values.

🎯 Run and iterate

Run the backtest, inspect results, then tweak your params. Rinse and repeat. That's the whole game.

This is basically the template you can use for any symbol and any strategy. Swap 'AAPL' for 'TSLA' or 'BTC-USD', adjust the strategy logic, change the commission — the data prep stays the same. Want to go deeper on the strategy side? Check out our hands-on tutorial with backtesting.py or the broader complete Python backtesting guide for more context on pipelines and gotchas.

One last thing. When bt.plot() opens, don't just look at the equity curve. Scroll through the trade markers. Ask yourself: did the strategy buy near lows consistently? Did it ride winners or cut winners too early? The plot tells a story that print(results) never will. Now go run it — and break things, because that's how you'll actually learn this library.

Backtesting.py Data Checklist

Before you run a single backtest, walk through this checklist. It takes less than a minute and prevents the most common data-related failures.

Quick checklist before you run Backtest

  • Columns named exactly Open, High, Low, Close, and optionally Volume
  • A DatetimeIndex is recommended, sorted in ascending order
  • No missing values in OHLC columns (or appropriately filled)
  • yfinance MultiIndex flattened when necessary
  • Numeric data types for price and volume columns
  • Verify with print(df.columns.tolist()) and print(df.head())

Algotradium: An Easier Way to Backtest & Automate

By now, you've seen exactly how picky backtesting.py is about column names, data types, and indexes. If your goal is simply to learn the DataFrame structure, Backtesting.py is more than enough. But if you'd rather not fetch, clean, and standardize data from yfinance every time, Algotradium can simplify that part of the workflow.

Algotradium handles data ingestion and preparation behind the scenes, so you can focus on strategy logic. It's a practical option when you want to move from a tested idea to a live bot with fewer moving parts.

If you're comparing tools before settling on one, our Best Python Backtesting Libraries guide puts everything side by side. Many traders use both Backtesting.py and Algotradium.

At the end of the day, the best tool is the one that helps you test more ideas without burning time on data chores. Algotradium makes that easier.

FAQ

Yeah, I know this part of backtesting isn't as glamorous as writing strategies. But honestly, almost every "it doesn't work" message in Backtesting.py starts with a data issue. So here are the questions I see most often, with the fixes that actually work.

What data format does Backtesting.py require?

Backtesting.py wants a pandas DataFrame with at least four columns named exactly Open, High, Low, and Close. Volume is optional but common. A DatetimeIndex is recommended for time-series data, although Backtesting.py can also work with a monotonic RangeIndex. Columns should hold numeric values — floats or ints — with no missing NaN entries in OHLC.

Why so strict? Because the engine slices rows by time and assumes clean OHLC data when it calculates equity, drawdowns, and orders. If you give it messy data, the numbers you get back are meaningless.

import pandas as pd
from backtesting import Backtest

df = pd.read_csv('AAPL.csv', parse_dates=['Date'], index_col='Date')
print(df.columns)  # ['Open','High','Low','Close','Volume']
print(df.index)    # DatetimeIndex, sorted ascending
print(df.isna().sum()) # check for missing values
What are Backtesting.py DataFrame columns?

The answer is simple: Open, High, Low, Close, and optionally Volume. The column names are case sensitive, so lowercase open won't work.

ColumnRequired?What it means
OpenYesPrice at the start of the period
HighYesHighest traded price during the period
LowYesLowest traded price during the period
CloseYesPrice at the end of the period
VolumeOptionalNumber of shares or contracts traded

You can keep additional columns such as Adj Close or Symbol in your DataFrame. The official documentation says the DataFrame can have extra columns that your strategy uses. However, Backtesting.py expects the required columns to use their expected names. If a required column is missing or incorrectly named, the backtest may raise a KeyError.

Are Backtesting.py column names case sensitive?

Yes. Absolutely. 'open' is not the same as 'Open'. The library checks the exact string. If you downloaded data from somewhere that gives you lowercase or uppercase column names, you'll get an error.

This catches typos before they silently mess up your backtest. And the fix is one line:

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})
print(df.columns.tolist())
How do I prepare a pandas DataFrame for Backtesting.py?
  1. Convert the index to a DatetimeIndex with pd.to_datetime() if needed.
  2. Sort the index with df.sort_index() if it isn't already ascending.
  3. Rename the columns to the exact capitalized names.
  4. Handle missing OHLC values with df.dropna(subset=["Open","High","Low","Close"]).
  5. Remove duplicate timestamps if present.

For the full picture of how this fits into a real workflow, the Python Backtesting: The Complete Guide walks through data collection, cleaning, and building a first backtest.

How do I rename DataFrame columns for Backtesting.py?

Use pandas' rename method:

df = df.rename(columns={
    'open': 'Open',
    'high': 'High',
    'low': 'Low',
    'close': 'Close',
    'volume': 'Volume'
})

Before running the backtest, verify your column names with print(df.columns.tolist()). This can help identify common issues such as spelling mistakes, unexpected capitalization, or trailing spaces in column names.

How do I load Yahoo Finance data into Backtesting.py?

This one is super common because yfinance is the go-to free data source. The tricky part is that yfinance may return a MultiIndex for the columns when you download multiple tickers. Check for a MultiIndex and flatten it when necessary:

import yfinance as yf
import pandas as pd

df = yf.download('AAPL', start='2020-01-01', end='2023-12-31')

if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

df.index = pd.to_datetime(df.index)
df = df[['Open','High','Low','Close','Volume']]
df = df.dropna(subset=["Open", "High", "Low", "Close"])

After that, you can pass df straight into Backtest(). If you want to see the whole flow with a simple moving-average strategy, check out this Python backtesting tutorial — it walks through the same steps with real code.

What are common Backtesting.py data errors?

Here are the usual suspects and how to fix them quickly:

Error or symptomLikely causeQuick fix
KeyError: 'Open'Column missing or wrong capitalizationRename columns to exact Open etc.
"Data must be sorted ascending"Index has out-of-order datesdf.sort_index()
TypeError about indexIndex is strings or integersdf.index = pd.to_datetime(df.index)
NaN values in OHLCHolidays, halts, or bad data feedsdf.dropna(subset=["Open","High","Low","Close"])
Weird column namesMultiIndex not flattenedif isinstance(df.columns, pd.MultiIndex): df.columns = df.columns.get_level_values(0)

A quick sanity check before every backtest:

assert list(df.columns[:4]) == ['Open', 'High', 'Low', 'Close']
df = df.dropna(subset=["Open", "High", "Low", "Close"])
Got any other questions?

Drop them in the comments — or better yet, play with the data and see what errors pop up. That's the fastest way to learn. And if this DataFrame dance feels tedious, you can always let a platform like Algotradium handle the plumbing for you while you focus on strategy logic.


backtesting.py data columns backtesting.py dataframe columns backtesting.py open high low close volume backtesting.py OHLCV backtesting.py data format backtesting.py column names backtesting.py capitalization backtesting.py case sensitive backtesting.py dataframe Backtesting.py Open High Low Close Volume