A price history that lies
When a company splits its shares 4-for-1, every holder gets four shares for each one they had, and the price falls to a quarter. Nobody lost anything, but the raw history shows a -75% day. A cash dividend does the same on a smaller scale: on the ex-dividend date the price drops by about the dividend. A strategy that reads raw prices will see crashes that never hurt anyone. The fix is to adjust the history: rescale all the days before the event so the series is continuous and comparable with today's prices.
import pandas as pd
index = pd.bdate_range("2026-03-02", periods=6)
raw = pd.Series([480.0, 484.0, 488.0, 123.0, 124.5, 126.0], index=index, name="close") # 4-for-1 split on 5 March
print(raw.pct_change().round(4).tolist())
# [nan, 0.0083, 0.0083, -0.748, 0.0122, 0.012] the -74.8% day never happened to anyoneAdjusting for a split
Only the days before the split change: divide their prices by the ratio (4 for a 4-for-1 split). Today's prices stay as quoted, so the adjusted series ends at the real current price. Build a True/False mask with index >= split_date (a string works against a DatetimeIndex) and use where, which keeps a value where the mask is True and takes the replacement elsewhere. Volume goes the other way: four times as many shares trade after the split, so earlier volumes are multiplied by the ratio. A reverse split uses a ratio below 1 and the same formulas.
split_date = "2026-03-05"
adjusted = raw.where(raw.index >= split_date, raw / 4) # the days before the split, divided by 4
print(adjusted.tolist()) # [120.0, 121.0, 122.0, 123.0, 124.5, 126.0]
print(adjusted.pct_change().round(4).tolist()) # [nan, 0.0083, 0.0083, 0.0082, 0.0122, 0.012] no more crash# Volume goes the other way: older days are multiplied
volume = pd.Series([1000, 1100, 900, 4200, 4000, 4300], index=index)
print(volume.where(volume.index >= split_date, volume * 4).tolist()) # [4000, 4400, 3600, 4200, 4000, 4300]Adjusting for a dividend
A dividend is smaller than a split, so the adjustment is a factor close to 1: 1 - dividend / previous close, where the previous close is the last close before the ex-dividend date. Multiply every price before the ex-date by that factor. The close just before the ex-date then drops by exactly the dividend, the ex-day move shows only what the market really did, and returns computed from the adjusted series include the dividend. This adjusted close is what return calculations should use. Put the factor in its own small function so that you can test it and reuse it.
def dividend_factor(prev_close, dividend):
return 1 - dividend / prev_close
prices = pd.Series([100.0, 101.0, 102.0, 100.8, 101.5, 102.5], index=index) # a 1.50 dividend, ex-date 5 March
ex_date, dividend = "2026-03-05", 1.5
prev_close = prices[prices.index < ex_date].iloc[-1] # 102.0, the last close before the ex-date
factor = dividend_factor(prev_close, dividend)
print(round(factor, 6)) # 0.985294
adjusted = prices.where(prices.index >= ex_date, prices * factor)
print(adjusted.round(4).tolist()) # [98.5294, 99.5147, 100.5, 100.8, 101.5, 102.5]
print(prices.pct_change().round(4).tolist()[3], adjusted.pct_change().round(4).tolist()[3]) # -0.0118 0.003close and adj_close) and say which one a calculation uses.- A split divides the old prices by the ratio and multiplies the old volumes by it; only the days before the split change.
- A dividend multiplies the prices before the ex-date by
1 - dividend / previous close. - Use
where(index >= date, adjusted)with a mask, not a loop; compute returns from adjusted prices, trade at raw ones.
Next up: line plots with matplotlib, drawing the clean price series you now have.
Lab · what's graded
- Make the prices comparable across a split with
adjust_for_split25 pts - Scale the old volumes with
adjust_volume_for_split25 pts - Compute the dividend adjustment factor with
dividend_factor25 pts - Remove the dividend drop from the history with
adjust_for_dividend25 pts