Adjusted vs unadjusted stock prices are the most common reason a backtest produces returns that are quietly wrong. Here is the failure in concrete form. A daily-bar backtest holds AAPL through August 2020. On Friday, August 28, the close is 499.23. On Monday, August 31, the close is 129.04. The return calculation books a 74.2% loss in one session, the stop-loss fires, and the equity curve never recovers. Nothing like that happened in the market. Apple split its stock 4-for-1 with an ex-date of August 31, 2020, and the bars were unadjusted, so a bookkeeping change in share count got scored as a crash.
The opposite mistake is quieter. Run everything on adjusted prices and the position-sizing code will happily "buy" AAPL in 2015 at prices that never printed, in share counts no order could have filled, while a skip-stocks-under-5-dollars filter tests against price levels that never existed. Both errors are silent. The bars look plausible, the code runs, the result is wrong.
This post covers how the two series relate, how to compute the adjustment factors yourself so the transformation is reproducible, when each series is the correct one, and how to request them from an API. If bars themselves are new to you, start with what OHLCV data is and come back.
What adjusted and unadjusted stock prices actually are#
An unadjusted bar records what actually traded. The open, high, low, and close are prices participants paid on that date, and volume is the share count that changed hands. If you want to know what a fill would have cost on a given day in 2019, the unadjusted bar is the answer.
An adjusted series is the same history rescaled so it reads as continuous across corporate actions. The most recent bar is left untouched, and every bar before an action's ex-date is multiplied by a factor that removes the artificial jump. A 4-for-1 split stops looking like a 75% drop, and a cash dividend stops looking like a small loss on a day the holder was actually made whole in cash.
Think of them as two coordinate systems for one instrument. Neither is more correct in general; they answer different questions. The bugs come from mixing them, or from not knowing which one a provider handed you.
A worked example: one split and one dividend#
AAPL's 4-for-1 split had an ex-date of August 31, 2020. The split factor is 1/4: every price field before the ex-date is multiplied by 0.25, and volume is multiplied by 4 so dollar volume stays comparable across the boundary.
| Date | Unadjusted close | Adjusted close |
|---|---|---|
| 2020-08-28 | 499.23 | 124.81 |
| 2020-08-31 | 129.04 | 129.04 |
The naive return across those two rows is 129.04 / 499.23 - 1, a 74.2% loss. The adjusted return is 129.04 / 124.81 - 1, a 3.4% gain, which is what the stock actually did that day.
Dividends use a different factor with the same mechanics. When a stock goes ex-dividend its price opens lower by roughly the dividend, but the holder receives the cash, so total return is unchanged. The standard factor is 1 minus the dividend divided by the prior unadjusted close. AAPL went ex-dividend on November 6, 2020, paying 0.205 per share against a prior close near 119.03, so the factor is 1 - 0.205 / 119.03, about 0.99828.
Factors compound. The cumulative adjustment for any date is the product of the factors of every action that comes after it. A bar from July 2020 sits before both events above, so its cumulative factor is 0.25 x 0.99828 = 0.24957. That property is what makes adjustment reproducible: given the same corporate-actions list and the same formula, two people compute identical series.
In pandas, over a frame of unadjusted daily bars indexed by date with close column c:
import pandas as pd
actions = [
{"ex_date": "2020-08-31", "type": "split", "ratio": 4.0},
{"ex_date": "2020-11-06", "type": "dividend", "cash": 0.205},
]
def adjustment_factors(bars, actions):
f = pd.Series(1.0, index=bars.index)
for a in actions:
before = bars.index < pd.Timestamp(a["ex_date"])
if a["type"] == "split":
f[before] *= 1.0 / a["ratio"]
else:
prev_close = bars.loc[before, "c"].iloc[-1]
f[before] *= 1.0 - a["cash"] / prev_close
return f
bars["adj_close"] = bars["c"] * adjustment_factors(bars, actions)
Two details matter. The dividend factor must divide by the unadjusted prior close; using an already-adjusted close there produces the wrong ratio. And adjusted volume uses only the reciprocal of the split factors (multiply pre-split volume by 4 here), because dividends don't change share counts.
When to use adjusted vs unadjusted stock prices#
Use the adjusted series for anything that is a function of returns: signals, moving averages and other indicators, volatility estimates, cumulative performance. An indicator fed unadjusted AAPL bars sees a phantom 75% crash on August 31, 2020 and emits garbage for its entire lookback window afterward.
Use the unadjusted series for anything anchored to the actual print: converting dollars into share counts, minimum-price or round-lot rules, per-share commissions, matching a price against what an 8-K or a news story said on that date, and options strikes. A simulator that tracks a cash balance and a share count needs the prices orders would really have filled at, plus explicit handling of the actions themselves: multiply the share count on a split, credit the cash on a dividend.
A workable division of labor: compute returns and signals on adjusted prices, simulate cash and shares on unadjusted prices, and keep the factor series around to convert between the two. Backtesting systems built this way stay reproducible because every transformation is explicit.
Requesting both series from a historical OHLCV data API#
Daily bars around the split boundary, from SiftingIO's historical stocks endpoint:
curl --compressed -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/hist/stocks/AAPL/bars?interval=1d&start=2020-08-24&end=2020-09-04"
Historical bar endpoints require gzip. Without an Accept-Encoding: gzip header the API returns 406 gzip_required; curl's --compressed flag sets the header and decompresses for you.
SiftingIO returns US stock bars either adjusted (split- and dividend-adjusted) or unadjusted, and exposes the underlying deterministic adjustment factors, so you can re-derive or audit the adjusted series with the math above instead of trusting an opaque adjusted column. The historical stocks docs cover the bar parameters, and the US stocks product page lists the corporate-actions coverage: splits, dividends, mergers, spin-offs, ticker changes, delistings.
Whatever provider you use, run the split test before trusting a series: pull daily AAPL bars across August 2020. A smooth series through the 31st is adjusted; a 4x cliff is unadjusted.
Common pitfalls#
Double adjustment. Applying your own factors to a series that was already adjusted shrinks pre-split history twice. The symptom for AAPL is an August 28, 2020 close near 31.20 instead of 124.81, because applying the 0.25 split factor a second time to the already-adjusted 124.81 leaves it four times too small. The split test above catches this before it reaches a return calculation.
Split-adjusted but not dividend-adjusted. Plenty of sources adjust for splits only. For a stock yielding 3%, ignoring dividend factors understates the total-return series by roughly 3 points a year, and mixing a split-only series from one source with a fully adjusted one from another injects a drift that compounds over long histories. Check for a small downward step at a known ex-dividend date to tell which kind you have.
Volume adjusted in the wrong direction. Prices before a 4-for-1 split multiply by 0.25 while volume multiplies by 4. Getting that backwards distorts dollar-volume and liquidity filters by a factor of 16, and the symptom is a liquidity screen that drops a heavily traded stock in exactly its pre-split years.
The math in this post is small enough to verify by hand, and that is the point: with unadjusted bars plus a corporate-actions list, an adjusted series is something you can compute, check, and reproduce rather than take on faith. The free tier includes a month of historical bars to test against. Start building free

