会说谎的价格历史
公司按 4 比 1 拆股时,每位股东每持有 1 股会得到 4 股,股价跌到四分之一。没有人亏钱,但原始历史里却出现了一个 -75% 的交易日。现金分红在较小的规模上也是如此:除息日股价大约下跌一个股息的幅度。读取原始价格的策略会看到从未伤害任何人的暴跌。解决办法是复权:对事件之前的所有日子重新缩放,使序列连续,并与今天的价格可比。
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 anyone拆股复权
只有拆股之前的日子会变化:把它们的价格除以比率(4 比 1 拆股就是 4)。当前价格保持报价不变,因此复权后的序列以真实的当前价格结尾。用 index >= split_date 建立真/假掩码(字符串可以与 DatetimeIndex 比较),再用 where:掩码为真处保留原值,其余位置取替换值。成交量方向相反:拆股后成交的股数是之前的四倍,所以更早的成交量要乘以比率。合股使用小于 1 的比率,公式相同。
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]分红复权
分红比拆股小,因此复权是一个接近 1 的因子:1 - 股息 / 前一收盘价,其中前一收盘价是除息日之前的最后一个收盘价。把除息日之前的每个价格乘以这个因子。于是除息日前一天的收盘价恰好下降一个股息的数额,除息日的涨跌只反映市场真实的行为,而用复权序列算出的收益率包含了股息。收益率计算应该使用这个复权收盘价。把因子放进一个独立的小函数,方便测试和复用。
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.003算收益用复权价,交易用原始价复权价格用于衡量收益和跨时间比较,并不是你实际支付的价格:订单按当天报价的原始价格成交,而较早的复权价格可能低于当时股票的真实成交价。请同时保留
close 和 adj_close 两列,并说明每个计算用的是哪一列。- 拆股把旧价格除以比率、把旧成交量乘以比率;只有拆股之前的日子会变。
- 分红把除息日之前的价格乘以
1 - 股息 / 前一收盘价。 - 用掩码做
where(index >= date, 复权值),不要用循环;用复权价算收益,用原始价交易。
下一节:用 matplotlib 画折线图,把现在已经干净的价格序列画出来。
实验:评分内容
- 用
adjust_for_split让拆股前后的价格可比25 分 - 用
adjust_volume_for_split换算旧成交量25 分 - 用
dividend_factor计算分红的调整因子25 分 - 用
adjust_for_dividend从历史中消除除息造成的下跌25 分