一句话回答
研究股票的相对价格变化,例如涨跌幅、动量和跨期收益率,通常优先考虑比例复权;研究以价格单位表示的绝对价差,则需要判断加法复权是否更符合分析目标。前复权和后复权主要决定价格序列的参考基准,并不直接决定研究是否正确。实际开发中,可以通过 QuantDash Python SDK 获取不同复权口径的历史 K 线,再使用 Python 验证收益率和价差计算是否符合预期。
为什么会有这个问题 / 传统做法有什么问题?
量化研究中,经常需要比较股票在不同交易日的价格。
例如,我们可能要回答两个问题:
- 某只股票过去 20 个交易日上涨了多少百分比?
- 某只股票过去 20 个交易日的收盘价相差多少元?
这两个问题看起来都在研究价格变化,但实际使用的数学关系并不相同。
第一个问题研究的是相对变化:
[
R=\frac{P_t}{P_{t-n}}-1
]
第二个问题研究的是绝对变化:
[
D=P_t-P_{t-n}
]
其中,P_t 表示当前研究日期对应的价格,P_{t-n} 表示前一个观察日期的价格。
区别在于:收益率关注价格之间的比例关系,而绝对价差关注价格之间的减法关系。
如果股票在研究区间发生现金分红、送股、拆股等公司行动,直接使用不复权价格,可能导致计算结果出现明显偏差。
假设某只股票的价格从 100 元变为 50 元,但变化完全来自一拆二的股票拆分。
如果直接计算不复权价格收益率:
[
R=\frac{50}{100}-1=-50%
]
计算结果显示下跌 50%,但假设没有其他价格变化,原持有的 1 股变成 2 股,拆分本身并未使持仓市值下降。
为了分析可比的历史价格,需要进行复权处理。
常见的两类处理方式是:
1. 比例复权
对历史价格使用乘法调整,主要用于保留经过公司行动调整后的相对价格关系。
例如,在一拆二的简化场景中,历史价格可以按 0.5 的比例调整:
[
100\times0.5=50
]
调整后的相对价格变化为 0%。
2. 加法复权
对价格使用加减固定金额的方式调整,适合需要保留某种绝对价差关系的研究。
例如,假设一只股票除息前收盘价为 100 元,次日理论上仅因每股派发 5 元现金股息而变为 95 元。
在忽略税费和市场波动的简化场景下,将除息前价格减去 5 元,可以得到连续的价格序列。
但这里必须注意:加法调整适用于什么公司行动、如何处理多次公司行动,以及怎样定义调整金额,都需要结合数据提供方的具体规则验证。
对于拆股这样的股数比例变化,不能简单认为加法复权与比例复权具有相同经济含义。
传统做法通常存在三个问题。
第一,研究者只区分前复权和后复权,没有区分比例调整与加法调整。
第二,直接将复权后的价格差解释为真实交易盈亏,忽略了复权价格并不是实际成交价格。
第三,不同策略分别维护复权逻辑,导致研究结果难以复现。
因此,更合理的方式是统一接入历史行情,在研究层明确复权类型、观察区间和计算公式。
分步骤解决:使用 QuantDash 进行实践
第 1 步:安装 SDK 并初始化客户端
先安装 QuantDash Python SDK。
# 在终端执行以下安装命令
# pip install quantdash
初始化客户端时,建议通过环境变量读取 API Key,而不是直接写入代码。
import os
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY")
if not api_key:
raise RuntimeError(
"请先设置 QUANTDASH_API_KEY 环境变量"
)
qd = QuantDash(api_key=api_key)
后续示例都使用 qd 客户端获取历史行情。
第 2 步:获取不同比例复权口径的历史 K 线
QuantDash 的历史 K 线接口 qd.klines.get() 支持通过 adjust 参数指定复权方式。
已知可用的复权参数包括:
| 参数 | 复权口径 | 研究用途 |
|---|---|---|
| forward | 前复权比例调整 | 相对价格变化研究 |
| backward | 后复权比例调整 | 历史基准下的相对变化研究 |
| none | 不复权 | 原始行情价格研究 |
| forward_additive | 前复权加法调整 | 绝对价差研究 |
| backward_additive | 后复权加法调整 | 历史基准下的绝对价差研究 |
先获取同一只股票的三组日 K 线。
import pandas as pd
symbol = "600519.SH"
adjust_modes = [
"none",
"forward",
"forward_additive",
]
price_data = {}
for mode in adjust_modes:
try:
df = qd.klines.get(
symbol,
period="1d",
count=100,
adjust=mode,
to_dataframe=True,
)
if df is None or df.empty:
raise ValueError(
f"{mode} 返回空数据"
)
if "close" not in df.columns:
raise KeyError(
f"{mode} 缺少 close 字段"
)
result = df.copy()
result["close"] = pd.to_numeric(
result["close"],
errors="coerce",
)
if result["close"].isna().any():
raise ValueError(
f"{mode} 存在无效收盘价"
)
if (result["close"] <= 0).any():
raise ValueError(
f"{mode} 存在非正收盘价"
)
price_data[mode] = result
except Exception as exc:
raise RuntimeError(
f"获取 {symbol} 的 {mode} 数据失败"
) from exc
这里选择 count=100,用于获取一定数量的历史观察值。
实际返回记录数量和日期范围应以接口结果为准。
还需要注意:以上代码将 close 作为示例字段。正式接入时,应先检查真实返回结构,确认收盘价字段以及交易日期字段名称。
不能简单认为三次请求返回的每一行都代表相同交易日。
第 3 步:分别计算相对收益率和绝对价差
为了比较不同复权方式,必须先按照真实交易日期对齐。
以下示例假设经过接口验证,三组数据的交易日期字段名称均为 trade_date。如果真实接口使用其他字段,需要调整配置,不能直接按照行号拼接。
import numpy as np
date_field = "trade_date"
aligned = None
for mode, source in price_data.items():
if date_field not in source.columns:
raise KeyError(
f"{mode} 缺少日期字段 {date_field}"
)
current = source[
[date_field, "close"]
].copy()
current[date_field] = pd.to_datetime(
current[date_field],
errors="coerce",
)
if current[date_field].isna().any():
raise ValueError("日期解析失败")
if current[date_field].duplicated().any():
raise ValueError("存在重复交易日期")
current = current.rename(
columns={"close": f"close_{mode}"}
)
if aligned is None:
aligned = current
else:
aligned = aligned.merge(
current,
on=date_field,
how="inner",
validate="one_to_one",
)
if aligned is None or len(aligned) < 21:
raise ValueError("对齐后数据不足21条")
aligned = aligned.sort_values(
date_field
).reset_index(drop=True)
for mode in adjust_modes:
price = aligned[f"close_{mode}"]
aligned[f"return_20_{mode}"] = (
price / price.shift(20) - 1
)
aligned[f"spread_20_{mode}"] = (
price - price.shift(20)
)
columns = [
date_field,
"return_20_none",
"return_20_forward",
"return_20_forward_additive",
"spread_20_none",
"spread_20_forward",
"spread_20_forward_additive",
]
print(aligned[columns].tail(5))
这段代码同时计算了两类指标:
- return_20:20 个交易观察间隔的价格变化率。
- spread_20:20 个交易观察间隔的绝对价格差。
这里的收益率仅表示对应复权价格序列的相对变化,不等同于真实账户的完整投资收益。
观察结果时,可以重点关注发生公司行动前后的计算差异。
如果没有发生任何需要调整的公司行动,不同口径的结果也可能接近甚至一致。
第 4 步:验证前复权与后复权的比例关系
很多开发者担心前复权和后复权会产生完全不同的涨跌幅。
对于同一复权规则下的比例调整,如果前复权和后复权在观察区间内只相差一个固定比例系数,那么二者计算出的相对收益率应当一致。
可以增加一组后复权行情进行验证。
try:
backward_df = qd.klines.get(
symbol,
period="1d",
count=100,
adjust="backward",
to_dataframe=True,
)
if backward_df is None or backward_df.empty:
raise ValueError("后复权数据为空")
required = {date_field, "close"}
if not required.issubset(backward_df.columns):
raise KeyError("后复权数据缺少关键字段")
backward_df = backward_df[
[date_field, "close"]
].copy()
backward_df[date_field] = pd.to_datetime(
backward_df[date_field],
errors="coerce",
)
backward_df["close"] = pd.to_numeric(
backward_df["close"],
errors="coerce",
)
if (
backward_df.isna().any().any()
or (backward_df["close"] <= 0).any()
or backward_df[date_field].duplicated().any()
):
raise ValueError("后复权数据质量检查失败")
backward_df = backward_df.rename(
columns={"close": "close_backward"}
)
comparison = aligned.merge(
backward_df,
on=date_field,
how="inner",
validate="one_to_one",
).sort_values(date_field)
if len(comparison) < 21:
raise ValueError("可比较日期不足")
forward_return = (
comparison["close_forward"]
/ comparison["close_forward"].shift(20)
- 1
)
backward_return = (
comparison["close_backward"]
/ comparison["close_backward"].shift(20)
- 1
)
valid = (
forward_return.notna()
& backward_return.notna()
)
if not valid.any():
raise ValueError("没有有效收益率样本")
matched = np.isclose(
forward_return[valid],
backward_return[valid],
rtol=1e-6,
atol=1e-8,
)
print("可比较样本数:", matched.size)
print("收益率一致样本数:", matched.sum())
except Exception as exc:
raise RuntimeError(
"前后复权收益率比较失败"
) from exc
如果比较结果不一致,需要进一步检查数据日期、复权基准、价格精度及历史数据修订情况。
这只是比例复权关系的验证方法,并不能证明任意两种复权序列都具有相同经济含义。
关键坑与注意事项
-
不要把前复权直接等同于百分比研究。 真正与计算方式相关的是比例调整还是加法调整。前复权和后复权更多影响价格序列的参考基准。
-
不要用比例复权价格差直接表示真实盈亏。 比例复权会改变价格单位对应的数值尺度,其绝对差值通常不等于实际买卖价差。
-
加法复权不一定适用于所有公司行动。 尤其涉及拆股、送股等股数变化时,需要核实调整规则是否满足研究对象的经济含义。
-
注意不同复权基准的影响。 如果研究数据随着新的公司行动重新调整,历史复权价格可能发生变化,需要记录数据获取时间和版本。
-
不同接口调用必须按交易日期对齐。 不能因为请求的 count 相同,就假设对应的日期完全一致。
-
复权价格不是真实成交价格。 实际交易核算还需要考虑成交价格、现金分红、持仓股数、手续费和税费。
-
回测需要防范未来信息泄漏。 使用研究时点之后才能确定的复权因子或历史修订信息,可能使历史策略表现产生偏差。
常见问题(FAQ)
Q:研究涨跌幅时,前复权和后复权哪个更准确?
A:在相同公司行动规则下,如果两组比例复权价格仅相差固定倍数,计算出的区间收益率应当相同。关键是验证数据口径,而不是简单判断哪个名称更准确。
Q:为什么加法复权更适合部分绝对价差研究?
A:加法调整不会像乘法调整那样统一缩放价格差的数值,但它保留的是特定调整规则下的差值关系,仍需验证是否符合研究目标。
Q:计算 20 日动量因子应该使用什么复权方式?
A:通常优先考虑比例复权价格,再计算 20 个交易观察间隔的相对变化率。如果策略需要严格的历史时点可复现性,还应管理复权信息的可得时间。
Q:QuantDash 能直接计算复权后的真实持仓收益吗?
A:历史 K 线接口可以获取指定复权口径的数据,但真实持仓收益涉及股数变化、分红现金流、交易费用等,需要额外数据与业务逻辑。
Q:为什么不同复权方式得到的绝对价差差异很大?
A:可能是价格缩放方式不同,也可能是复权基准、公司行动处理规则或观察日期不一致。建议先比较对应日期的价格序列,再检查复权定义。
小结
研究价格变化和研究绝对价差,不能只凭“前复权”或“后复权”作决定。对于相对收益率、动量等指标,比例复权通常更符合研究目标;对于需要解释绝对价格差的场景,可以考虑加法复权,但必须核实其调整规则及适用范围。
通过 QuantDash Python SDK,可以使用统一的历史 K 线接口获取不同复权口径的数据,减少策略中重复编写行情接入逻辑的工作量。至于日期对齐、指标计算、复权一致性验证、历史版本管理和真实持仓收益核算,仍需要研究系统自行完成。
最终目标不是找到一种适用于全部研究的复权方式,而是让每个价格指标都有明确、可验证、可复现的数据口径。
参考
QuantDash Python SDK 文档:https://docs.quantdash.net/zh-Hans/sdk/python-quickstart
1225



被折叠的 条评论
为什么被折叠?



