假设一只股票在某个交易日收盘价为 100 元,经过分红送转后,下一交易日的收盘价变成了 50 元。如果直接计算收益率:
[ R_t=\\frac{P_t}{P_{t-1}}-1 ]
得到的结果是:
[ R_t=\\frac{50}{100}-1=-50% ]
但这并不意味着投资者真的损失了 50% 的资产价值。价格变化中可能包含了除权除息的影响,而不是单纯的市场涨跌。
这正是复权数据存在的意义。
然而,即使使用了复权价格,前复权、后复权和不复权数据计算出的收益率也可能不完全一致。问题的关键不在于哪一种方式计算错误,而在于:不同复权方式采用了不同的价格调整基准,且复权后的价格序列并不一定在所有时点保持相同的数值。
理解这一点,需要从复权机制、收益率计算和数据使用场景三个层面展开。
一、复权改变的是价格序列,而不是历史交易事实
股票发生现金分红、送股、转增股本或配股等权益事件时,除权除息后的交易价格可能与前一交易日的价格出现明显差异。
如果直接使用交易所公布的原始价格计算收益率,就可能把权益调整造成的价格变化误认为市场收益。
复权通过调整历史价格,尽可能使不同交易日之间的价格具有可比性。
常见的处理方式包括:
- 不复权: 保留原始交易价格,不对历史价格进行复权调整。
- 前复权: 通常以当前价格水平为基准,对较早的历史价格进行调整。
- 后复权: 通常以历史起点为基准,对后续价格进行调整。
需要注意,前复权和后复权并不是两种不同的市场收益定义,而是构造可比较价格序列的不同方法。
QuantDash 官方 Python 示例仓库列出了 forward、backward、none、forward_additive 和 backward_additive 等复权参数。其中,前三者分别对应前复权、后复权和不复权,具体使用方式应以对应版本的官方接口文档为准。
二、为什么前复权和后复权的价格不一样,收益率却可能一样?
这是理解复权计算最重要的一步。
假设原始收盘价序列为:
| T1 | 100 |
| T2 | 50 |
| T3 | 55 |
假设 T1 到 T2 之间发生了一次权益事件。为了简化演示,假设该事件对应的历史价格调整比例为 0.5。
对 T1 之前的价格乘以 0.5,可以得到一组示意性的前复权价格:
| T1 | 50 |
| T2 | 50 |
| T3 | 55 |
如果以 T1 的价格为基准,对后续价格乘以 2,则可以构造一组示意性的后复权价格:
| T1 | 100 |
| T2 | 100 |
| T3 | 110 |
这里的调整比例仅用于说明计算原理,并不代表某种具体权益事件的完整计算规则。
观察 T2 到 T3:
前复权收益率为:
[ R_{\\text{forward}} =\\frac{55}{50}-1=10% ]
后复权收益率为:
[ R_{\\text{backward}} =\\frac{110}{100}-1=10% ]
虽然两种复权方式下的价格完全不同,但收益率相同。
原因是,在同一段不跨越复权调整边界的价格区间内,如果两组价格之间仅相差一个固定的正比例系数,那么这个系数会在收益率计算中抵消。
设:
[ P_t’=cP_t ]
其中 (c>0) 为常数,则:
[ R_t’ =\\frac{P_t’}{P_{t-1}'}-1 ]
代入后得到:
[ R_t’ =\\frac{cP_t}{cP_{t-1}}-1 =\\frac{P_t}{P_{t-1}}-1 =R_t ]
因此,价格绝对值不同,并不必然意味着收益率不同。
真正需要关注的是:两组价格之间的调整关系是否在所研究的区间内保持一致。
三、收益率在哪些情况下会出现差异?
1. 计算区间跨越了权益事件
如果使用不复权价格,除权除息日前后的价格可能存在机械性跳变。
这时,直接计算的收益率会同时包含市场价格变化和权益事件对价格的影响。
而复权价格通常会对这类价格变化进行调整,因此两种结果可能明显不同。
但这不意味着复权收益率在任何情况下都等于投资者的实际总回报。
例如,现金分红是否计入收益、股息是否再投资、税费如何处理,都会影响最终的投资回报口径。
2. 计算区间跨越了不同的调整基准
前复权和后复权的区别不仅体现在价格水平上,还体现在不同历史区间的调整方式上。
如果研究区间跨越了权益事件,或者比较的是不同版本、不同基准日期的复权数据,就需要确认这些数据在研究区间内是否仍然具有相同的比例关系。
如果不存在统一的比例系数,收益率就可能出现差异。
3. 使用了不同的复权算法
复权不一定只有一种计算实现。
例如,比例复权和加法复权采用不同的价格调整形式。
比例复权可以抽象为:
[ P_t’=a_tP_t ]
加法复权则可以抽象为:
[ P_t’=P_t+b_t ]
其中 (a_t) 和 (b_t) 是与时间或权益事件有关的调整参数。
对于比例复权,如果同一区间内的比例系数相同,收益率通常保持不变。
对于加法复权,即使加上的调整值在区间内相同,收益率也不一定保持不变:
[ R_t’ =\\frac{P_t+b}{P_{t-1}+b}-1 ]
一般来说:
[ \\frac{P_t+b}{P_{t-1}+b} \\neq \\frac{P_t}{P_{t-1}} ]
因此,不能简单地认为所有复权算法都会产生相同的收益率。
4. 数据精度和舍入方式不同
即使两个数据源使用相同的复权方向,也可能因为以下因素出现细微差异:
- 复权因子的精度不同。
- 中间计算过程采用不同的舍入方式。
- 复权因子在不同阶段应用。
- 返回价格保留的小数位数不同。
- 数据更新时重新计算了历史价格。
当收益率以较高精度计算时,这些差异可能表现为小数点后若干位的变化。
如果策略使用了严格的收益率阈值、价格交叉条件或细粒度统计指标,即使很小的数值差异,也可能影响边界样本的分类。
四、用 Python 验证:价格不同不代表收益率一定不同
在金融数据分析中,可以通过同一标的、同一周期和同一时间范围的数据,对不同复权方式进行比较。
QuantDash 官方示例仓库提供了以下调用方式:
from quantdash import QuantDash
qd = QuantDash()
forward = qd.klines.get(
"600519.SH",
period="1d",
count=250,
adjust="forward",
to_dataframe=True,
)
backward = qd.klines.get(
"600519.SH",
period="1d",
count=250,
adjust="backward",
to_dataframe=True,
)
raw = qd.klines.get(
"600519.SH",
period="1d",
count=250,
adjust="none",
to_dataframe=True,
)
运行前,需要安装 SDK,并在环境变量 QUANTDASH_API_KEY 中配置有效的 API Key。
下面的代码使用 Pandas 计算各组数据的收盘价收益率,并检查结果。
import pandas as pd
def calculate_returns(df, name):
if df is None or df.empty:
raise ValueError(f"{name} 数据为空")
if "close" not in df.columns:
raise ValueError(
f"{name} 数据缺少 close 字段,"
f"实际字段为:{df.columns.tolist()}"
)
result = df.copy()
if "date" in result.columns:
date_col = "date"
elif "datetime" in result.columns:
date_col = "datetime"
else:
raise ValueError(
f"{name} 数据缺少可识别的日期字段"
)
result[date_col] = pd.to_datetime(result[date_col])
result = result.sort_values(date_col)
if result[date_col].duplicated().any():
raise ValueError(f"{name} 存在重复日期")
result["close"] = pd.to_numeric(
result["close"],
errors="coerce",
)
result = result.dropna(subset=["close"])
if (result["close"] <= 0).any():
raise ValueError(
f"{name} 存在非正收盘价,需先检查数据"
)
result["return"] = result["close"].pct_change()
return result[[date_col, "close", "return"]]
forward_returns = calculate_returns(forward, "前复权")
backward_returns = calculate_returns(backward, "后复权")
raw_returns = calculate_returns(raw, "不复权")
print(forward_returns.tail())
print(backward_returns.tail())
print(raw_returns.tail())
需要特别注意:上面的日期字段检查只是对常见数据结构的兼容处理,实际字段名称应根据所使用版本返回的 DataFrame 确认。
此外,三个请求分别取最近 250 条数据,并不自动保证返回结果在日期上完全一致。正式比较前,还应对齐共同交易日期,确认起止区间和数据覆盖范围一致。
五、如何判断计算结果是否合理?
建议从以下三个方面检查。
第一,检查价格序列。
观察权益事件附近的原始价格和复权价格,判断价格跳变是否符合所采用的复权口径。
第二,检查收益率序列。
使用同一日期的相邻收盘价计算收益率,而不是直接比较不同日期或不同基准下的价格。
第三,检查研究目标。
如果目的是研究技术指标,可以选择合适的复权价格序列。
如果目的是衡量投资者实际获得的收益,则应明确现金分红、股息再投资、交易费用和税费等处理方式,不能直接把任意一种复权价格收益率视为完整的投资总回报。
复权方式本身不是收益率定义。只有在价格口径、时间区间、数据版本和收益率定义都明确的前提下,不同结果之间的比较才有意义。
QuantDash 官方文档
- QuantDash 技术文档 — 查看 Python SDK、REST API 和数据接口说明。


