回测必须使用比例法(涨跌幅复权法)来处理分红和配股导致的股价变动,因为它能准确反映“分红再投资”下的真实总收益率。文章通过例子证明,加减法复权会错误地低估复利效果,导致回测结果失真。最后推荐了 yfinance 和 TuShare 等提供比例法复权数据的 Python 数据源。
原文很啰嗦,gemini-2.5-pro 撰写,改为新模型重写。
为什么必须用“比例法”(涨跌幅复权)?
在量化回测中,股票价格会因为分红、拆股等行为出现“断层”。为了保证回测的真实性,我们必须对价格进行修复(复权)。比例法(也称涨跌幅复权法)是目前业界公认的标准方法。
通俗案例:什么是比例法?
核心逻辑:假设你拿到的分红,立刻在当天以收盘价重新买入了这只股票(分红再投资)。它保证了复权后的价格走势,与你满仓持有的总资产增长率完全一致。
【场景演示】
- 第1天:你花 100元 买入1股。
- 第2天(除息日):公司发 10元 现金分红。
- 股价自然回落到 90元。
- 你的资产:90元股票 + 10元现金 = 100元(没亏没赚)。
- 第3天:股价上涨 10%,从90元涨到了 99元。
【比例法是如何记录的?】 比例法会通过调整历史价格,让K线图体现出这 10% 的真实涨幅。
- 它计算出的第3天涨幅:
(99 - 90) / 90 = 10%。 - 它构建的复权价格序列,会让你看到一条平滑上涨的曲线,真实反映了如果你把那10元分红买成股票,你的总资产实际上涨了10%。
为什么不能用“加减法”(价格复权)?
核心逻辑:加减法只是简单粗暴地把分红的钱“加”回到股价上。它最大的问题是破坏了收益率的真实性,导致回测失效。
通俗案例:加减法的陷阱
沿用上面的例子:
- 第2天:股价90元,分红10元。加减法处理后的价格 = 90 + 10 = 100元。
- 第3天:股价涨到99元(市场真实涨幅10%)。加减法处理后的价格 = 99 + 10 = 109元。
【问题出在哪?】 请计算加减法处理后的“涨幅”:
$$(109 - 100) / 100 = 9\%$$结论:市场明明涨了 10%,但用加减法算出来只有 9%。 因为加减法无视了复利效应——它默认那10元分红变成了死钱,没有参与第3天的上涨。随着时间推移,这种误差会越来越大,导致你的策略回测结果严重失真(通常会低估收益)。
推荐:支持“比例法”复权的 Python 数据源
在 Python 中获取数据时,请认准关键词 Adjusted (Adj) 或 复权。
Yfinance (Yahoo Finance) —— 国际通用,完全免费
最适合新手和研究美股/全球市场,默认提供比例法复权数据。
import yfinance as yf
# auto_adjust=True 会自动下载比例法复权后的数据
# 这里的 Close 字段已经是复权后的价格
df = yf.download('AAPL', start='2023-01-01', auto_adjust=True)
print(df.head())
Tushare —— 国内首选,数据规范
最适合研究 A 股,提供清晰的前复权(qfq)和后复权(hfq)选项。
import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()
# adj='qfq' 代表使用前复权(比例法)
df = pro.daily(ts_code='600519.SH', start_date='20230101', adj='qfq')
print(df.head())
Baostock —— 免费开源,无需积分
适合 A 股基础数据获取,同样支持复权参数。
import baostock as bs
import pandas as pd
bs.login()
# adjustflag="2" 代表前复权
rs = bs.query_history_k_data_plus("sh.600519",
"date,close,adjustflag",
start_date='2023-01-01',
frequency="d",
adjustflag="2")
# ...后续处理代码...
bs.logout()