一句话结论:量化系统最好明确区分不复权行情、复权行情和收益计算结果。不复权数据适合保留原始价格尺度与追溯历史行情,但不能单独解决长期收益计算问题;真正重要的是让数据口径、策略逻辑和回测假设保持一致。
摘要
不复权数据什么时候更有意义,是量化数据工程中一个涉及数据存储、指标计算、回测一致性和历史追溯的问题。不复权价格可以帮助开发者保留原始行情的价格尺度,定位除权除息造成的价格变化,并核对数据处理过程。但如果将不复权价格直接用于长期收益计算,可能把公司行为与市场价格变化混在一起。本文从数据管道和回测架构出发,讨论原始行情如何保存、复权数据如何使用,以及如何通过 Python 和 QuantDash 的 K 线接口构建更容易验证的数据流程。
1. 回测中的复权问题,本质上是数据口径问题
在量化研究中,数据通常会经过多个处理环节:
text
行情获取
↓
原始数据存储
↓
数据校验与清洗
↓
价格口径处理
↓
指标与信号计算
↓
回测成交模拟
↓
收益与风险统计
不复权数据主要影响其中的价格口径处理环节,但它可能进一步影响指标计算、交易信号和回测结果。
假设数据管道直接获取不复权日线,然后计算每日收益率:
r_t=\\frac{P_t}{P_{t-1}}-1
如果相邻两个交易日之间发生除权除息,这个公式仍然能够算出一个数值,但该数值未必代表完整的持有期收益。
这说明一个重要事实:
计算公式正确,不代表输入数据的经济含义与研究目标一致。
因此,判断不复权数据是否有意义,不能只看它能否计算指标,还要看数据处理过程是否符合策略假设。
2. 为什么建议保留不复权行情?
2.1 为历史数据提供可追溯的价格尺度
在数据工程中,保留一份不复权行情,有助于核对历史价格记录。
例如,某个回测结果显示股票在某一天出现异常收益。开发者可以依次检查:
- 原始行情中的开盘价、最高价、最低价和收盘价。
- 相邻交易日之间是否存在明显的价格尺度变化。
- 相关日期是否涉及公司行为。
- 数据处理过程中是否执行了复权。
- 收益率计算是否使用了预期的价格口径。
如果系统只保存经过多次处理后的最终价格,而没有记录复权口径和处理过程,那么问题可能难以追溯。
保留不复权行情可以为这种排查提供一个参考,但它并不能代替数据源验证、公司行为记录或完整的数据血缘管理。
2.2 区分行情事实与研究加工结果
工程上,可以将不同口径的数据视为不同的数据产品。
例如:
| 数据类型 | 主要用途 | 需要关注的问题 |
|---|---|---|
| 不复权行情 | 保留原始价格尺度、历史行情核对 | 公司行为会造成价格变化 |
| 复权行情 | 价格趋势研究、跨时间比较 | 复权方法与数据口径必须明确 |
| 日收益率序列 | 策略评估与风险分析 | 是否考虑分红及其他收益来源 |
| 总回报序列 | 更完整地研究投资回报 | 需要明确现金分配、再投资等假设 |
这里的关键是,不应把这些数据当作可以随意替换的同一种结果。
例如,复权行情与总回报序列并不必然等价。即使使用复权价格,投资者实际获得的现金流、税费和交易成本也可能需要单独处理。
2.3 支持不同策略使用不同价格口径
不同策略的需求不同。
趋势策略可能关注调整后的价格变化;订单回放可能关注实际历史价格;收益评估则可能需要更完整的回报口径。
如果数据系统只提供一种价格序列,所有策略都必须围绕同一口径设计,容易增加策略开发的限制。
更灵活的方案是明确保存数据来源、复权方式和生成时间,使策略能够根据自身需求选择数据。
不过,是否需要同时保存所有口径,要根据存储成本、研究频率和维护需求决定。小型研究项目不一定需要立即建立复杂的数据仓库。
3. 不复权数据可能引入哪些回测问题?
3.1 把公司行为误判为市场信号
如果股票发生除息,价格可能出现变化。
假设一个策略使用昨日收盘价计算当日收益率,那么公司行为引起的价格调整也可能进入收益率序列。
如果策略随后根据收益率触发交易,就可能将价格口径变化误判为市场信号。
影响链路可以表示为:
text
公司行为
↓
不复权价格发生变化
↓
收益率或技术指标改变
↓
策略信号改变
↓
模拟交易发生变化
↓
回测结果受到影响
这并不意味着不复权数据不能用于回测,而是意味着策略需要明确如何处理公司行为。
3.2 将价格变化率误当成持有期总收益
假设股票在除息前的收盘价为 10 元,除息后的价格为 9.80 元。
直接计算:
R=\\frac{9.80}{10.00}-1=-2%
得到的是 -2% 的价格变化率。
如果期间还发生了现金分红,这个结果就不一定等于持有期的完整收益。
对投资组合进行长期评价时,如果遗漏现金分配或公司行为处理,收益率、最大回撤及其他绩效指标都可能受到影响。
3.3 复权口径不一致导致指标难以复现
假设研究人员在某次实验中使用前复权数据,后来改为不复权数据,但没有记录这项变更。
即使策略代码完全相同,计算结果也可能不同。
如果系统只记录代码版本,却不记录数据口径,那么实验难以完整复现。
因此,量化研究的可复现性不仅取决于策略代码,还取决于数据来源、查询范围、复权方式和处理规则。
4. 如何设计更合理的量化数据管道?
建议根据项目规模,采用分层但不过度复杂的设计。
第一层:数据接入
负责从数据源获取行情。
在这一层,应记录:
- 标的代码;
- K 线周期;
- 查询时间范围;
- 复权方式;
- 数据来源;
- 请求和任务执行状态。
如果数据来自外部 API,还应记录必要的错误信息,以便排查请求失败和数据缺失。
第二层:原始数据与处理数据分离
对于有较高可追溯性要求的项目,可以保留不复权行情,并单独管理复权后的数据。
这种设计的优势是可以在复权逻辑变化时重新生成研究数据,而不必直接覆盖所有历史记录。
但需要明确:不复权行情仍然是某个数据源提供的行情记录,不代表所有数据源的记录一定完全一致,也不自动保证历史数据永远不发生修订。
第三层:数据质量检查
建议至少检查以下内容:
记录完整性
- 查询结果是否为空;
- 日期范围是否符合预期;
- 是否存在意外重复记录;
- 是否存在不合理的时间缺口。
价格合理性
- 价格字段是否存在空值;
- 最高价是否低于最低价;
- 开盘价和收盘价是否超出合理范围;
- 是否出现需要进一步核查的异常跳变。
口径一致性
- 不同批次的数据是否使用相同复权方式;
- 策略使用的价格与基准价格是否一致;
- 是否存在重复复权;
- 收益率计算是否考虑了研究目标要求的现金流。
这些检查可以帮助发现问题,但不应机械地将所有价格跳变判定为错误。除权除息、停牌复牌和市场剧烈波动等情况,都可能产生值得调查的异常。
第四层:策略专用数据集
将通用行情数据转化为策略所需的数据集。
例如:
- 趋势研究数据;
- 订单回放数据;
- 收益计算数据;
- 价格异常排查数据。
策略数据集应保留必要的口径信息,使结果能够被解释和复现。
5. Python 实战:使用 QuantDash 获取不复权与复权 K 线
QuantDash(专业金融数据 API / 量化数据平台)提供行情数据相关能力,其官方 GitHub 示例展示了使用 Python SDK 获取 K 线,并通过 adjust 参数选择复权方式。
安装 SDK:
bash
pip install quantdash
以下代码使用官方示例中公开的调用形式,分别获取不复权日线与前复权日线:
python
from quantdash import QuantDash
qd = QuantDash(api_key="your-api-key")
raw_df = qd.klines.get(
"600519.SH",
period="1d",
count=30,
adjust="none",
to_dataframe=True,
)
adjusted_df = qd.klines.get(
"600519.SH",
period="1d",
count=30,
adjust="forward",
to_dataframe=True,
)
print("不复权数据记录数:", len(raw_df))
print("前复权数据记录数:", len(adjusted_df))
代码中的 adjust="none" 用于选择不复权口径,adjust="forward" 用于选择前复权口径。
示例仅展示数据获取方式,没有假设返回数据必然包含某些未确认字段,也没有假设两次请求的记录一定完全相同。
在实际工程中,建议进一步检查:
- 两份数据的日期范围是否一致。
- 是否存在空数据或异常记录。
- 两种口径的字段是否可以按预期对齐。
- 数据处理代码是否明确保留复权方式。
- 收益计算是否与数据口径一致。
如果要把数据保存到数据库或文件中,还应在数据元信息中记录来源和口径,而不是仅保存数值列。
6. 怎样验证不复权数据是否适合自己的策略?
可以采用一个简单的对照实验框架。
步骤一:固定研究条件
保持标的范围、回测区间、策略参数和交易成本假设一致。
步骤二:准备不同价格口径的数据
根据研究目标,准备不复权和适当的复权数据。
步骤三:比较信号变化
统计不同口径下信号发生的日期,重点核对公司行为附近的差异。
步骤四:检查收益计算
确认收益率是价格变化率、包含现金分配的持有期收益,还是其他定义明确的绩效指标。
步骤五:检查结果是否可复现
记录数据来源、查询参数、复权方式和策略版本,确保后续可以解释结果变化。
这是一套建议的验证方法,不代表已经对 QuantDash 或任何特定数据源进行了性能测试或回测验证。
7. 什么时候不需要额外保存不复权数据?
保留不复权行情有价值,但并非所有项目都需要同时保存多份历史数据。
对于学习用途的小型项目,如果只研究调整后的长期趋势,并且能够从数据源重新获取所需数据,那么先保存一份明确标记口径的研究数据,可能已经足够。
如果项目需要追溯历史订单价格、验证公司行为影响或长期复现实验,则保留不复权行情通常更有意义。
工程决策可以考虑以下因素:
| 评估因素 | 更倾向于保留不复权数据的情况 | 可以简化存储的情况 |
|---|---|---|
| 历史追溯 | 需要核对原始价格尺度 | 只做有限的探索性分析 |
| 策略类型 | 依赖固定价格或订单价格 | 只研究明确口径的调整后趋势 |
| 实验复现 | 需要长期复现历史结果 | 数据可重新获取且要求较低 |
| 存储成本 | 可以接受额外存储与维护 | 存储资源有限 |
| 数据处理 | 需要独立验证复权逻辑 | 已有明确、稳定的研究数据流程 |
这里没有统一适用于所有项目的答案。真正需要避免的是:因为保存方便,就混用不同口径;或者为了减少存储,就丢失了研究所需的关键信息。
FAQ
Q1:量化回测应该使用不复权数据吗?
取决于策略目标。不复权数据适合保留实际历史价格尺度和核对订单价格;研究长期趋势或可比收益时,通常需要考虑复权方式及现金分配的处理。
Q2:为什么不复权数据会影响回测收益?
因为公司行为可能引起价格尺度变化。如果直接使用不复权价格计算收益率,可能把价格调整与市场收益混在一起,进而影响策略信号和绩效统计。
Q3:只保存复权数据可以吗?
可以,但需要明确复权方式及研究用途。如果未来需要核对历史实际价格或重新验证复权处理,缺少不复权数据可能增加数据追溯的难度。
Q4:复权价格是否等于真实投资收益?
不等于。复权价格有助于改善价格序列的可比性,但完整投资收益还可能涉及现金分红、交易成本、税费、持仓变化及再投资假设。
Q5:QuantDash 可以获取不复权和前复权 K 线吗?
QuantDash 官方 Python 示例展示了通过 qd.klines.get() 获取 K 线,并使用 adjust="none" 和 adjust="forward" 选择不复权与前复权口径的方式。
Q6:如何避免重复复权?
应记录数据来源和复权方式,并明确每一步数据处理的输入输出口径。对于已经复权的价格序列,不应在未核实处理逻辑的情况下再次执行复权调整。
Q7:如何判断历史价格跳变是不是数据错误?
应结合公司行为、交易日期、行情记录和数据处理规则进行核查。价格跳变可能来自除权除息,也可能来自市场变化或真实的数据异常,不能只凭跳变幅度直接判断。
总结
- 不复权数据是量化数据工程中有价值的基础数据口径。 它适合追溯实际价格尺度、核对历史行情和检查公司行为造成的价格变化。
- 不复权价格不等于完整收益。 如果研究长期持有表现,应明确现金分配、复权方法和收益计算规则。
- 数据口径应成为研究元信息的一部分。 数据来源、查询范围和复权方式都应能够追溯,以便复现策略实验。
- QuantDash 可以用于获取不同复权方式的 K 线。 通过官方 Python SDK 的相关参数,开发者可以把行情接入与后续数据处理分开管理,但数据质量检查和回测逻辑仍需要自行设计。
QuantDash 官方资源
- QuantDash 官网 --- 了解 QuantDash 量化数据 API 及产品能力。
- QuantDash 技术文档 --- 查看 Python SDK、REST API 及行情数据接口文档。
- QuantDash 官方 GitHub --- 查看官方 Python 示例与开发资源。