yfinance Close vs Adjusted Close: Get Raw Prices
yfinance close vs adjusted close comes down to auto_adjust: it now defaults to True, so Close is already adjusted and Adj Close is dropped. Pass auto_adjust=False to get Yahoo's Close plus Adj Close.
- Pass auto_adjust=False to yf.download() or Ticker.history() to get Yahoo's Close and a separate Adj Close column.
- Yahoo's website Close is split-adjusted only; Adj Close is split and dividend adjusted, and yfinance's default Close matches the latter.
- Adjusted values change retroactively every time a new dividend is paid, so store raw close, adjusted close and the factor if you need reproducibility.
- Use raw close for price-level rules, sizing and broker comparisons, and adjusted close for returns and indicators.
- Recent yfinance versions return MultiIndex columns from download() even for one ticker, so select df['Close'] or droplevel before indexing.
How do I get the unadjusted close in yfinance?
Set auto_adjust=False. The call returns Open, High, Low and Close as Yahoo publishes them, plus a separate Adj Close column. Without it, recent versions rewrite Open/High/Low/Close with adjusted values and drop Adj Close, so Close no longer matches the site.
The same argument works on Ticker.history(). Check yf.__version__ first, because the defaults differ between releases (see the version section below).
import yfinance as yf
print(yf.__version__)
# both Close and Adj Close
df = yf.download("DVN", start="2021-09-01", end="2021-09-30",
auto_adjust=False, progress=False)
# same via Ticker, also includes Dividends and Stock Splits columns
h = yf.Ticker("DVN").history(start="2021-09-01", end="2021-09-30",
auto_adjust=False, actions=True)Why does yfinance Close not match the Close on Yahoo Finance?
Yahoo's historical data page shows two columns. 'Close' is adjusted for splits only. 'Adj Close' is adjusted for splits and dividends.
With auto_adjust=True, yfinance applies the dividend-adjusted ratio to all price columns. Its Close therefore equals the website's Adj Close, not the website's Close. This is the most common source of mismatches.
In the DVN example below, the 2021-09-09 values were Close 28.45 and Adj Close 27.96 when captured. The ratio is 27.96 / 28.45 = 0.9828, meaning dividends paid after that date reduce the older price by about 1.7%. Your Adj Close today will be lower than 27.96, because DVN has paid dividends since.
import yfinance as yf
df = yf.download("DVN", start="2021-09-08", end="2021-09-11",
auto_adjust=False, progress=False)
if df.columns.nlevels > 1:
df.columns = df.columns.droplevel(1) # drop the Ticker level
print(df.loc["2021-09-09", ["Close", "Adj Close"]])
# Close at capture time: 28.45, Adj Close at capture time: 27.96
# Today's Adj Close will be lower; Close stays 28.45 (no split since) How does the adjustment factor work?
Adjusted close is raw (split-adjusted) close multiplied by a cumulative factor. Each dividend contributes a factor of 1 - D / P_prev, where D is the dividend per share and P_prev is the close on the trading day before the ex-dividend date. Every price before that ex-date is multiplied by it.
The most recent prices have a factor of 1.0. Each new dividend multiplies all older factors by another number slightly below 1, which is why historical adjusted values drift lower over time even though nothing about the past changed.
You can rebuild the series from Ticker.actions (the Dividends and Stock Splits columns) to verify what Yahoo returns. Small differences, typically in the third or fourth decimal, come from Yahoo's rounding of the inputs.
import pandas as pd
import yfinance as yf
t = yf.Ticker("DVN")
h = t.history(start="2021-01-01", auto_adjust=False, actions=True)
# 1) dividend factor rebuilt from dividends
factor = pd.Series(1.0, index=h.index)
for d, div in h.loc[h["Dividends"] > 0, "Dividends"].items():
prev = h.loc[h.index < d, "Close"]
if prev.empty:
continue
factor[factor.index < d] *= 1 - div / prev.iloc[-1]
h["adj_rebuilt"] = h["Close"] * factor
h["diff"] = h["adj_rebuilt"] - h["Adj Close"]
print(h["diff"].abs().max())
# 2) as-traded price: undo splits that happened AFTER each date
s = h["Stock Splits"].replace(0, 1.0)
after = s[::-1].cumprod()[::-1].shift(-1).fillna(1.0)
h["raw_traded_close"] = h["Close"] * afterWhat changed between yfinance versions, and why are my columns a MultiIndex?
Ticker.history() has long defaulted to auto_adjust=True. yf.download() defaulted to False in older 0.2.x releases and flipped to True around 0.2.51, so code that used to return Adj Close silently stopped doing so after an upgrade. Never rely on the default: pass auto_adjust explicitly.
Newer download() releases also return MultiIndex columns (Price, Ticker), even for one symbol. Code like df['Close'] then yields a one-column DataFrame instead of a Series, and df['Adj Close'] can raise a KeyError when auto_adjust is True.
| Symptom | Likely cause | Fix |
|---|---|---|
| No 'Adj Close' column | auto_adjust=True (default) | Pass auto_adjust=False |
| Close lower than the website's Close | Dividend adjustment already applied | Use auto_adjust=False |
| df['Close'] returns a DataFrame | MultiIndex columns from download() | df['Close']['DVN'], df.droplevel(1, axis=1), or multi_level_index=False if your version supports it |
| Same code, different numbers after upgrade | download() default changed | Pin the version and set auto_adjust explicitly |
| Old adjusted values differ from last month's | New dividend paid since | Store raw close and factor |
Should I use raw close or adjusted close in a backtest?
Use whichever matches the question the code asks. Prices that represent real money at a real moment need raw values. Anything that measures change over time across ex-dividend dates needs adjusted values.
Never mix the two in one calculation. A common bug is computing returns from raw Close, which shows a fake drop on every ex-dividend day and understates total return. The opposite bug is applying a rule such as price < 20 to adjusted data: older adjusted prices are artificially low, so the rule triggers on dates when the real price was above the threshold.
| Task | Use | Reason |
|---|---|---|
| Daily or total returns | Adjusted close | Dividends are folded into the price series |
| Moving averages, momentum, volatility | Adjusted close | Avoids artificial gaps on ex-dates |
| Price threshold rules (price < $X) | Raw (as-traded) close | Threshold refers to the real quote |
| Position sizing, share counts, lot rounding | Raw close | You buy shares at the real price |
| Comparing with broker fills or statements | Raw close | Fills are at actual prices |
| Dollar volume or liquidity filters | Raw close x raw volume | Adjusted price distorts historical dollar volume |
| Long-history indicator plus a price filter | Both, in separate columns | Signal from adjusted, filter from raw |
How do I store prices so a backtest is reproducible?
Adjusted values are not stable. Every new dividend rewrites the history, so a result saved last quarter cannot be reproduced from a fresh download. Save the raw columns, the factor and the fetch metadata instead of only the adjusted series.
Computing the factor as Adj Close / Close lets you recover either series later, and the yfinance version in the file tells you which behaviour produced it.
import datetime as dt
import pandas as pd
import yfinance as yf
def fetch(symbol, start, end=None):
h = yf.Ticker(symbol).history(start=start, end=end,
auto_adjust=False, actions=True)
h.index = h.index.tz_localize(None) # keep dates, drop tz
s = h["Stock Splits"].replace(0, 1.0)
after = s[::-1].cumprod()[::-1].shift(-1).fillna(1.0)
out = pd.DataFrame({
"close_split_adj": h["Close"], # Yahoo site 'Close'
"close_as_traded": h["Close"] * after, # reverses splits
"adj_close": h["Adj Close"],
"adj_factor": h["Adj Close"] / h["Close"],
"dividend": h["Dividends"],
"split": h["Stock Splits"],
"volume": h["Volume"],
})
out["fetched_at"] = dt.datetime.utcnow().isoformat()
out["yf_version"] = yf.__version__
return out
fetch("DVN", "2021-01-01").to_csv("DVN_prices.csv")What if the numbers still disagree with Yahoo's page?
First confirm which column you are comparing: the page's Close against yfinance's auto_adjust=False Close, and the page's Adj Close against the auto_adjust=True Close. Then check the date range, because the page's Adj Close shifts after each dividend, and the exchange timezone, because intraday requests can land on the wrong session.
Yahoo data itself sometimes has errors, such as prices off by 100x (pence versus pounds) or splits applied twice. Passing repair=True to history() or download() asks yfinance to detect and fix those cases. It needs scipy installed, can alter values, and should be checked rather than trusted blindly.
For anything that drives real decisions, compare against a second source such as your broker's historical data or an exchange file. Flag any day where the raw close differs by more than a tick or two and investigate corporate actions on that date.
h = yf.Ticker("DVN").history(start="2021-01-01", auto_adjust=False,
repair=True) Common questions
Is yfinance Close split-adjusted or dividend-adjusted?
With auto_adjust=False, Close is split-adjusted only, matching Yahoo's website Close. With auto_adjust=True, Close is both split and dividend adjusted, equal to the site's Adj Close.
Why do my adjusted prices change from one day to the next?
Each new dividend multiplies all earlier prices by a factor slightly below 1. The older history is therefore rescaled whenever a dividend is paid, even though raw closes never change.
Can I get both Close and Adj Close in one call?
Yes. Use auto_adjust=False in yf.download() or Ticker.history() and both columns are returned. Setting it to True returns only adjusted OHLC values.
Do dividends and splits appear in the data?
Ticker.history() with actions=True (the default) includes Dividends and Stock Splits columns. Ticker.actions, Ticker.dividends and Ticker.splits expose the same data as separate series.
Why do returns from raw Close look wrong?
Raw close drops by roughly the dividend amount on the ex-date, which looks like a loss. Returns from raw Close across splits are far worse, showing a fake crash or spike, so compute returns from adjusted close.