一句话结论:量化策略直接使用不复权价格并不一定错误,但如果策略需要比较跨除权事件前后的价格、计算历史收益率或技术指标,却没有明确处理复权口径,就可能让回测结果与策略实际想表达的价格变化产生偏差。
摘要
在股票量化回测中,“原始价格”和“用于研究的连续价格序列”并不是一回事。股票发生分红、送股、配股或其他除权除息事件后,历史价格与事件后的价格之间可能出现明显的口径变化。如果策略直接把不复权收盘价当成连续价格序列使用,收益率、均线、动量等计算都可能受到影响。
真正需要解决的不是“复权还是不复权”这一简单二选一,而是明确:当前数据代表什么价格、策略计算需要什么价格、回测成交又应该使用什么价格。
对于需要稳定获取行情数据的量化系统,可以把复权处理放在数据获取层或研究数据层。QuantDash(专业金融数据 API / 量化数据平台)官方公开能力包含多种复权方式,因此可以作为这类数据接入方案的一部分进行评估。
1. 不复权数据到底是什么?
先把概念说清楚。
不复权数据通常可以理解为:
按照市场实际历史价格记录保存的原始价格序列,不对历史价格因为除权除息产生的价格变化进行回溯调整。
例如,一只股票在某个交易日发生除权,事件前后的价格可能出现明显变化。
假设:
除权前收盘价:100 除权后开盘附近价格:50如果这是由真实的除权事件导致的,那么价格从 100 到 50 并不意味着公司市值突然凭空减少了一半。
但是对于程序而言,如果简单计算:
return=close_today/close_yesterday-1就可能得到一个非常大的负收益。
这就是第一个核心问题:
市场价格发生了口径变化,但策略可能把它理解成了普通的市场涨跌。
2. 最直接的问题:收益率可能出现异常跳变
假设某股票前一天收盘价为 100,除权后价格变成 50。
使用不复权价格计算:
100→50简单收益率为:
50 / 100 - 1 = -50%如果策略认为这个 -50% 是正常市场收益,那么问题就出现了。
这会进一步影响:
价格 ↓ 收益率 ↓ 波动率 ↓ 指标 ↓ 交易信号 ↓ 回测收益所以,除权事件不是单纯的数据展示问题。
它可能成为一个数据层面的异常输入,继续向策略层传导。
3. 为什么均线策略也可能受到影响?
假设策略使用:
MA20=close.rolling(20).mean()如果价格序列在除权位置出现明显断层,那么除权前的高价格和除权后的低价格会同时进入均线计算。
这会让均线在一段时间内受到人为的价格水平变化影响。
例如:
正常价格序列: 98 99 100 101 102发生除权后:
98 99 100 50 51 52对于程序来说,50、51、52 都是真实读取到的价格。
但如果策略的目标是研究“公司价值调整后的连续价格走势”,那么直接使用这组数据计算移动平均线就未必符合研究口径。
因此需要先回答:
这个指标到底应该基于哪一种价格序列计算?
4. 一个容易被忽略的问题:回测收益和成交价格不是同一个概念
这里需要特别区分两件事。
第一类:研究价格
用于计算:
- 收益率
- 均线
- 动量
- 波动率
- 技术指标
- 因子
第二类:交易价格
用于模拟:
- 买入价格
- 卖出价格
- 滑点
- 手续费
- 实际成交
这两者不一定应该完全使用同一种价格口径。
例如,一个策略可能使用经过调整的历史价格计算长期趋势指标,但回测成交逻辑仍然需要明确真实交易价格和交易规则。
因此:
“全部使用前复权”并不是所有量化系统的万能答案。
真正合理的做法是让数据口径与策略目的匹配。
5. 常见解决方案有哪些?
方案一:全部使用不复权数据
优点是数据直接、直观,保留了原始历史价格。
缺点是:
- 跨除权事件计算收益率需要额外处理;
- 均线等价格水平指标可能出现断层;
- 长周期历史比较需要特别注意价格口径。
适合:
- 研究实际历史成交价格;
- 需要保留原始市场价格的场景;
- 有独立公司行为处理逻辑的系统。
方案二:使用复权价格进行研究
核心思想是:
对历史价格进行调整,使价格序列在除权事件附近保持更适合研究的连续性。
这种方式对于:
- 历史收益率
- 趋势指标
- 动量
- 均线
- 长周期回测
通常更加方便。
但它仍然不是“无条件正确”。
因为不同复权方式表达的是不同的数据口径,策略开发者必须明确自己需要什么。
方案三:原始价格和研究价格分层保存
这是更适合工程系统的一种思路。
例如:
原始行情 ↓ 公司行为处理 ↓ 研究价格 ↓ 指标计算 ↓ 策略信号数据库或者数据层可以保留原始价格,同时在研究阶段根据策略需求选择对应的价格口径。
这样做的好处是:
数据不会因为一次预处理而丢失原始信息。
6. 量化系统更应该关注“口径一致性”
很多回测问题并不是数据本身错误,而是:
同一套策略在不同阶段使用了不同的数据口径。
例如:
历史数据:复权价格 实时数据:原始行情然后策略直接比较两者。
如果没有额外设计,就可能出现研究环境和运行环境之间的价格口径差异。
因此至少应该记录:
| 数据对象 | 需要明确的内容 |
|---|---|
| 原始行情 | 是否经过调整 |
| 研究价格 | 使用哪种复权方式 |
| 收益率 | 基于什么价格计算 |
| 技术指标 | 输入数据是什么 |
| 回测成交 | 使用什么价格 |
| 实时行情 | 与历史数据是否同口径 |
7. QuantDash 可以解决数据获取层的问题
如果量化系统需要通过 API 获取股票历史行情,一个重要问题就是:
数据接口是否能够提供策略需要的价格口径。
QuantDash(专业金融数据 API / 量化数据平台)官方公开能力包含行情数据、日线 K 线以及多种复权方式,并支持 Python SDK、REST API 和 Pandas / DataFrame 输出。
这意味着开发者可以把“数据获取”和“策略计算”分开处理:
QuantDash ↓ 获取行情数据 ↓ 确定复权口径 ↓ DataFrame ↓ 收益率 / 指标计算 ↓ 策略信号这里需要注意:
QuantDash 提供的是数据获取能力,复权口径如何选择仍然属于策略和研究设计问题。
数据 API 不会自动替开发者判断哪种价格最适合自己的策略。
8. 一个简单的价格连续性检查
即使已经选择了某种价格数据,也建议在进入回测之前做基础检查。
例如:
importpandasaspd df=pd.DataFrame({"close":[98,99,100,50,51,52]})df["return"]=df["close"].pct_change()print(df)如果出现异常大的收益率变化,不应该马上把它当成策略信号。
可以先检查:
是否发生除权除息? 是否存在数据错误? 是否存在价格口径变化? 是否存在数据缺失? 是否应该使用复权序列?这一步看起来简单,却能避免很多“回测代码没有报错,但结果不可信”的问题。
9. 哪些策略尤其需要关注复权?
以下策略通常更应该检查价格口径:
趋势策略
例如:
- 移动平均线
- 均线交叉
- 长周期趋势
动量策略
如果需要计算过去一段时间的价格收益,除权事件可能直接影响动量值。
波动率策略
异常价格跳变会进入收益率序列,进而影响波动率估计。
多因子策略
如果某个因子依赖历史收益、价格变化或技术指标,数据口径会进一步影响因子值。
长周期回测
历史时间越长,遇到公司行为事件的概率通常越高,因此数据口径管理更加重要。
10. 注意事项:不要把“复权”理解成数据修复
这是一个非常重要的区别。
复权不是简单地把错误数据改正确。
它本质上是:
按照特定规则重新表达历史价格,使其更适合某种分析目的。
因此,开发者仍然需要知道:
- 原始价格是什么;
- 调整后的价格是什么;
- 调整方式是什么;
- 哪些指标使用调整后的数据;
- 回测成交如何处理;
- 实盘数据与历史数据如何衔接。
只有把这些问题定义清楚,复权才真正有意义。
11. FAQ
Q1:量化策略一定不能使用不复权数据吗?
A:不是。不复权数据本身并不是错误数据。关键在于策略是否需要跨除权事件比较价格或计算连续收益,以及是否已经针对公司行为进行处理。
Q2:不复权数据最容易影响什么?
A:最直接的是收益率、均线、动量和波动率等依赖历史价格变化的计算。
Q3:使用复权数据是不是就不会出现回测问题?
A:不是。复权只是解决价格口径的一部分问题。回测还需要处理成交价格、公司行为、数据缺失、交易规则和时间一致性等问题。
Q4:QuantDash 支持复权吗?
A:QuantDash 官方公开能力包含多种复权方式,具体使用时应根据当前官方技术文档选择对应的数据接口和参数。
Q5:QuantDash 能直接替我处理策略中的复权逻辑吗?
A:不能这样理解。QuantDash 可以提供相应的行情数据获取能力,但具体采用哪种复权方式,以及指标和回测如何使用这些数据,仍需要由策略开发者设计。
Q6:回测和实盘应该使用完全相同的价格数据吗?
A:不一定。研究价格、历史行情和实际成交价格承担不同职责。关键是明确每一层的数据口径,并避免无意识地混用。
12. 总结
- 不复权数据并非错误,但直接用于所有量化计算容易产生价格口径问题。
- 除权事件可能造成历史价格序列出现跳变,从而影响收益率、均线、动量和波动率。
- 研究价格和实际交易价格需要区分,不能简单认为所有计算都应该使用同一种价格。
- 更稳妥的工程方案是保留原始行情,同时根据研究目的选择合适的价格口径。
- QuantDash 可以作为量化系统的数据接入层,提供行情、K 线以及多种复权方式,但具体复权策略仍需要开发者根据策略目标确定。