量化的第一课不是“怎么赚钱”,而是“用什么眼睛看市场”。答案有点反直觉:不是价格,是收益率。
茅台一股 1700 块、平安银行一股 10 块——你能说谁“涨得快”吗?不能。价格的绝对数没有可比性。我们真正关心的是涨跌了百分之几,这就是收益率。它把不同价位、不同股票、不同时间统统拉到同一把尺子上。
所以拿到任何价格数据,量化的第一个动作永远是:把价格转成收益率。
两种算法,新手都要认识:
r = 今天价 / 昨天价 − 1。最直观。lr = ln(今天价 / 昨天价)。它有个好性质——可以直接相加:把每天的对数收益加起来,就等于整段的对数收益。统计建模里更常用。小波动时两者几乎相等(涨 1% ≈ 0.995%),只有大涨大跌时才看出差别。新手记住“有这两种、对数能加总”即可。
px = df["close"] # 复权收盘价
simple_ret = px.pct_change() # 简单收益率
log_ret = np.log(px / px.shift(1)) # 对数收益率
公司分红、送股的那天,股价会“凭空”跳水——因为一部分价值以现金/股份的形式发给你了。如果你直接用这种“未复权”价算收益,会低估真实回报。
解决办法是用复权价。差别有多大?同一段时间、同一只茅台:
| 口径 | 5 年总收益 |
|---|---|
| 前复权(正确) | +213.9% |
| 不复权(错误) | +188.2% |
差了整整 25 个百分点!取数时加一个参数就对了:
# 用 tushare 取前复权日线
df = ts.pro_bar(ts_code="600519.SH", api=pro,
start_date="20190101", end_date="20240101", adj="qfq")
复权有两种,区别在于“以哪一头为锚”:
| 前复权 qfq | 后复权 hfq | |
|---|---|---|
| 锚点 | 今天(最新价=现价) | 最早(历史价不变) |
| 历史价会变吗 | 会(每次分红重算) | 不会(永久固定) |
| 最适合 | 看盘/技术分析 | 回测/长期收益 |
复权只是给整条价格序列乘一个因子,而收益率是比值、不受整体缩放影响——所以前复权和后复权算出来的收益率序列、年化、回测结果完全一样。差别只在“价格的绝对水平和锚点”。
正因如此,做回测用哪个都行;很多量化偏好后复权,是因为它历史不随新分红变动、更适合可复现的长期回测。本课用 qfq,纯粹因为它最新价直观、最常见——新手只要记住:算收益率两者等价,关键是千万别用不复权。
量化一律用复权价(前复权 qfq / 后复权 hfq 皆可),绝不能用不复权价算收益——这是最容易、也最致命的新手错误之一。
“5 年涨了 213%”听着猛,但换算成年化收益(CAGR)才知道每年多快、才能和别的标的比。用几何平均(不是简单除以年数):
years = len(px) / 252 # 一年约 252 个交易日
cagr = (px.iloc[-1]/px.iloc[0])**(1/years) - 1 # 茅台这5年 ≈ 26.8%
看右图那张直方图:大多数交易日是小涨小跌(中间高),但两端有“肥尾”——偶尔的暴涨暴跌比正态分布预测的更频繁。这正是风险的来源:你以为很罕见的大跌,其实没那么罕见。下一关我们就来度量它。
1. 把股票换成平安银行(000001.SZ)、宁德时代(300750.SZ),比比谁年化高、谁尾巴更肥。
2. 把区间改成 2021–2024(偏熊的一段),看年化变成多少——体会“同一只票、区间不同,结论天差地别”。
3. 想一想:一只票“年化 26%”但“单日能跌 8%”,你拿得住吗?(这就引出下一关。)
量化研究的对象是收益率,不是价格;必须用复权价;年化(CAGR)是能跨标的比较的速度;收益率分布有肥尾,这是风险的源头。

pro_bar 取前复权日线,pct_change() 转收益率;用几何平均算年化(CAGR),用峰度(kurtosis)衡量肥尾——峰度越大,极端涨跌越频繁。三只票跑同一段时间再排队。import numpy as np
from scipy.stats import kurtosis
def cagr(px): # 几何年化
return (px.iloc[-1]/px.iloc[0])**(252/len(px)) - 1
for code, name in [("600519.SH","茅台"),("000001.SZ","平安"),("300750.SZ","宁德")]:
px = ts.pro_bar(ts_code=code, api=pro, adj="qfq",
start_date="20190101", end_date="20240101"
).sort_values("trade_date")["close"]
r = px.pct_change()
print(name, f"年化{cagr(px)*100:.1f}%", f"峰度{kurtosis(r.dropna()):.1f}")start_date、end_date,其余代码不动,重算茅台年化。同一只票、换个区间,结论可能完全翻转。px = ts.pro_bar(ts_code="600519.SH", api=pro, adj="qfq",
start_date="20210101", end_date="20240101"
).sort_values("trade_date")["close"]
print("茅台 2021-2024 年化", f"{cagr(px)*100:.1f}%")