Blog
6 min read

yfinance Backtesting: A Practical Guide

How to use yfinance for backtesting algorithmic trading strategies: what it is good for, the data pitfalls to watch, and how to go from raw historical prices to trustworthy results.

By Finny-AI team

yfinance is the most common way to pull free historical market data in Python, which makes it a natural starting point for backtesting. It is genuinely useful, but there are a few data pitfalls that quietly ruin backtests if you do not know about them. This guide covers both: how to use it well, and what to watch for.

What yfinance is good for

yfinance pulls historical prices for stocks, ETFs, indices, and more from a free public source. For research, prototyping, and backtesting daily or higher-timeframe strategies, that is often all you need. Pulling a few years of daily bars for a ticker takes one call:

python
import yfinance as yf

data = yf.download("SPY", start="2015-01-01", end="2024-01-01")
print(data.head())

From there you have open, high, low, close, and volume, which is enough to test a large share of strategy ideas.

The pitfalls that ruin backtests

1. Adjusted vs unadjusted prices

This is the big one. Splits and dividends create artificial jumps in raw prices. If you backtest on unadjusted data, a 2-for-1 split looks like a 50% overnight crash and your strategy will react to an event that never actually cost anyone money. Make sure you are using adjusted close prices, or that you understand exactly which series you are trading.

2. Survivorship bias

If you build a universe from tickers that exist today, you have quietly excluded every company that went bankrupt or was delisted. That makes any strategy look better than it would have been in real time, because the losers are missing from the sample. Be skeptical of results from a hand-picked list of names that happen to still be around.

3. Look-ahead bias

The most common coding bug in backtests is using information the strategy would not have had yet, such as acting on a bar's close at that same bar's open. It inflates returns and is easy to introduce by accident. The fix is disciplined bar-by-bar logic where every decision uses only data available at that point in time.

4. Rate limits and gaps

Because the data is free and public, it can rate-limit you, occasionally return gaps, or change format. For casual research this is fine. For anything you depend on, build in checks for missing bars and do not assume the data is clean.

From raw data to trustworthy results

Pulling data is the easy 10%. The other 90% is the backtest engine around it: applying entry and exit rules correctly bar by bar, handling positions and cash, and computing metrics that actually describe risk. A single total-return number hides everything that matters. You want return, risk, drawdown, and stability together.

This is exactly the plumbing that is tedious to write and easy to get wrong. Finny uses yfinance for historical data under the hood and pairs it with the engine_v2 backtester, which reports metrics across all four dimensions so you are not reading a cherry-picked figure. You describe the idea, and the data wiring and backtest logic are handled correctly for you.

Good habits, briefly

  • Use adjusted prices, or know exactly why you are not.
  • Test across long windows that include more than one market regime.
  • Never let a decision use data from the future.
  • Read drawdown and risk, not just return.

Getting started

If you would rather skip the data plumbing and get straight to testing ideas, install the CLI and run your first backtest. See the install guide and our step-by-step backtesting walkthrough.

— Finny-AI team