1. Splits and dividends: why yesterday's price changes today
Level 3: Adjustments and corporate actions
A closing price looks like the hardest piece of data there is: it really traded, it was printed on the tape and nobody can rewrite it. And yet the historical series of almost any share gets rewritten backwards every few years, all the way to its first day of trading. It is not a falsification. It is that the number printed on that day has stopped being comparable with today's.
A split changes nothing, and that is why it changes everything
In a two-for-one stock split, the company hands you one extra share for every one you already had and divides the price by two. If you had 100 shares at 80 euros, you end up with 200 shares at 40 euros. Before you were worth 8,000 euros and afterwards you are worth 8,000 euros. Not a cent has come in or gone out, the percentage of the company you own has not changed, and the earnings you are paying for have not changed.
The only thing that has changed is the unit of measurement. And there lies the problem, because a price series is a series of measurements in a unit that the split has just redefined halfway through. Comparing the previous day's close (80) with the next day's (40) is like comparing a distance measured in miles with the next one measured in kilometers and concluding that the road has got shorter.
The arithmetic of the adjustment factor
The fix consists of dividing every price before the event by the split ratio. With a two-for-one the ratio is 2:
- Previous day's unadjusted close: 80.00. Adjusted: 80.00 / 2 = 40.00.
- Unadjusted close a year ago: 62.50. Adjusted: 62.50 / 2 = 31.25.
- The effective day and everything after it: untouched.
Check that the returns do not move. Between that day a year ago and the previous day, unadjusted: 80.00 / 62.50 minus one, that is 28.0%. Adjusted: 40.00 / 31.25 minus one, 28.0% again. Identical. A correct adjustment is exactly that: an operation that changes the level of the series and leaves every return untouched except the one on the day of the event, which was precisely the one that was wrong.
With volume the symmetric thing happens: there are twice as many shares, so historical volume is multiplied by 2. If you adjust the price and forget the volume, any metric that mixes the two (turnover value, turnover ratio, your share of average volume) ends up divided by two across the entire earlier history with nothing to give it away.
What an unadjusted series does to your signal
import pandas as pd
# Raw series around a 2-for-1 split with effective date 2024-06-10.
precios = pd.Series(
[78.00, 79.50, 80.00, 40.60, 41.20, 40.90],
index=pd.to_datetime([
"2024-06-05", "2024-06-06", "2024-06-07",
"2024-06-10", "2024-06-11", "2024-06-12",
]),
)
# The daily return on the raw series invents a crash.
print(precios.pct_change().round(4))
# 2024-06-10 -0.4925 <- there was no such fall: there was a splitThat minus 49.25% is not an isolated bad data point you can ignore: it is the worst kind of bad data point, because it is plausible. A twelve-month momentum filter sees a 49% fall and sends the stock into the bottom decile. A moving-average crossover sees the price far below its long average and sells. An outlier detector flags it as a 49% fall and, if your cleaning trims extreme moves, it trims it and leaves it at a minus 20% that is just as false. And a volatility measure goes through the roof for the following twelve months, so any risk-based position sizing shrinks that stock for a whole year.
The dividend rewrites the past too
When a company pays a dividend, on the ex-dividend date the share starts out worth less because that money is no longer inside it. The shareholder has lost nothing: they have the lower price and the cash in their account. But the price series only sees the fall. The usual factor is one minus the dividend divided by the last close before the ex date, and it is applied to every earlier price.
serie = pd.Series(
[41.00, 41.50, 42.00, 41.80],
index=pd.to_datetime([
"2024-09-16", "2024-09-17", "2024-09-18", "2024-09-19",
]),
)
dividendo, fecha_ex = 0.50, pd.Timestamp("2024-09-19")
cierre_previo = serie.loc[serie.index < fecha_ex].iloc[-1] # 42.00
factor = (cierre_previo - dividendo) / cierre_previo # 0.9880952
ajustada = serie.copy()
ajustada[serie.index < fecha_ex] *= factor
print(round(serie.pct_change().iloc[-1] * 100, 2)) # -0.48 false
print(round(ajustada.pct_change().iloc[-1] * 100, 2)) # +0.72 correctThe raw series says you lost 0.48% on the ex date. The adjusted one says you gained 0.72%, and that is what really happened: you collected 0.50 and the price only fell 0.20. With a small quarterly dividend the difference looks anecdotal; repeated four times a year for twenty years it stops being so, and in a high-yield stock it eats half the return you think you are measuring.
A real case: Apple, August 31, 2020
Apple carried out a four-for-one stock split effective on August 31, 2020. The previous close was around $499 and the first post-split close came in near $129. On an unadjusted series that is a fall of roughly 74% in one session, the largest in the company's history, on a day when no shareholder lost anything at all.
Think what that does to a 200-session moving average. The average was still computed on three-figure prices, on the order of $300, while the price moved down below $130. The signal crosses to the downside on August 31 and cannot cross back up until the 200-session window has refreshed almost all of its contents: months out of the stock, not because of any reading of the market, but because of a change in the unit of measurement.
To go deeper into this lesson
- Stock splitin the glossary