量化回测复权数据的工程实践:从 API 获取到数据质量验证

一句话结论: 复权数据不能只看获取是否成功,还要验证价格口径、时间范围、数据完整性和策略计算的一致性。一个可靠的回测数据流程,应当把行情获取、复权配置、质量检查和交易模拟分开管理,并确保每次研究都能追溯所使用的数据与参数。

摘要

量化回测中的复权问题,往往不是选择一个参数就结束了。数据接口成功返回 K 线,并不代表历史价格适合直接进入策略。标的代码、交易日期、缺失记录、复权口径和公司行为都可能影响最终的因子与收益计算。本文从数据工程角度,介绍如何设计可维护的复权行情处理流程,给出 Pandas 数据质量检查示例,并说明 QuantDash 的 Python SDK 如何用于获取不同复权方式的 K 线数据。

1. 回测数据管道中,复权只是一个环节

一个常见的量化研究流程如下:

text 复制代码
数据 API
   ↓
行情获取
   ↓
数据标准化
   ↓
复权口径管理
   ↓
数据质量检查
   ↓
因子计算
   ↓
交易信号
   ↓
回测引擎
   ↓
绩效分析

如果复权处理被放在一个缺乏规则的预处理脚本中,后续维护通常会遇到几个问题:

  • 无法确定某次回测到底使用了哪种复权方式。
  • 不同策略使用了不同价格口径,却没有明确标记。
  • 数据更新后,历史指标和回测结果发生变化。
  • 数据缺失被误认为交易信号。
  • 原始市场价格与调整后的分析价格混用。

这些问题的共同原因是:数据获取逻辑、数据质量规则和策略逻辑没有清晰分层。

因此,设计复权数据流程时,首先要解决的不是选择哪种复权参数,而是定义数据在系统中的职责。

2. 先明确三个不同的数据对象

2.1 原始行情

原始行情用于保存数据来源返回的价格记录及其时间信息。

建议保留:

  • 标的代码。
  • 行情时间。
  • 原始价格字段。
  • 数据来源。
  • 获取时间。
  • 请求参数。
  • 原始数据或可复现的数据快照。

原始行情的价值在于可追溯。即使后续决定使用不同的复权方式,也不必丢失最初获取的数据记录。

但需要注意,保存了原始行情,并不意味着所有历史信息都已经具备点时属性。如果数据服务商后续修订历史记录,研究者仍需通过快照或版本记录确认自己使用的是哪一份数据。

2.2 复权行情

复权行情用于根据指定规则调整价格序列。

例如,同一标的可以根据策略需求获取前复权、后复权或不复权数据。

这一层应当明确记录:

text 复制代码
标的代码
行情周期
复权方式
查询范围
数据来源
获取时间

如果同一份 DataFrame 被不同策略复用,却没有保存复权方式,后续很难判断指标差异究竟来自策略变化还是输入数据变化。

2.3 策略输入数据

策略输入数据是经过验证、可以进入因子计算和回测逻辑的数据。

这一层除了价格口径,还应确认:

  • 数据是否存在重复记录。
  • 时间字段是否能够正确排序。
  • 价格是否存在非预期的缺失值。
  • 标的代码是否统一。
  • 指标计算是否存在未来数据依赖。
  • 成交模拟是否使用合适的价格字段。

将这三个对象分开,可以降低数据处理代码与策略代码之间的耦合。

3. 复权数据最值得检查的质量问题

3.1 缺失记录

如果某个交易日缺少 K 线,策略可能出现两种问题。

第一种是直接使用缺失值计算指标,导致指标无法正常生成。

第二种是程序自动跳过缺失记录,使策略误以为相邻两条记录之间没有其他需要考虑的交易日。

需要注意,非交易日没有 K 线并不等于数据缺失。因此,缺失检查必须结合市场交易日历,而不是简单地要求自然日连续。

3.2 重复记录

同一标的和同一交易日期出现重复记录,可能导致:

  • 均线计算重复计入某些价格。
  • 收益率计算产生额外记录。
  • 回测引擎重复处理交易日。
  • 数据合并后行数异常增长。

在处理之前,应先明确数据的业务主键。对于日 K 数据,通常需要结合标的代码和交易日期检查唯一性,但具体字段名称应以实际返回结构为准。

3.3 异常价格

常见检查包括:

  • 价格字段是否为数值类型。
  • 是否存在非预期的零值或负值。
  • 高价、低价、开盘价和收盘价之间是否满足基本关系。
  • 相邻交易日价格是否出现值得核查的异常变化。

不过,价格跳变并不必然意味着数据错误。

公司行为、停复牌及其他市场因素都可能导致价格变化。因此,异常检测的作用是定位需要进一步调查的记录,而不是自动修改所有异常数据。

3.4 复权口径混用

这是最容易被忽略的问题之一。

假设一个研究模块使用前复权价格计算均线,另一个模块使用不复权价格计算当前价格偏离。如果没有明确的数据口径设计,最终因子可能比较的是两个不同基准下的价格。

建议把复权方式作为数据配置的一部分,并在计算过程中保持一致。

4. 使用 Pandas 建立基础数据检查

下面先用一个通用的数据检查函数演示工程思路。

它不依赖任何特定数据服务商的方法,可以在确认实际返回字段后用于检查行情 DataFrame。

python 复制代码
import pandas as pd

def validate_daily_bars(
    df: pd.DataFrame,
    symbol_col: str,
    date_col: str,
    price_cols: list[str],
) -> pd.DataFrame:
    required = [symbol_col, date_col, *price_cols]

    missing_cols = [
        col for col in required
        if col not in df.columns
    ]

    if missing_cols:
        raise ValueError(
            f"缺少必要字段: {missing_cols}"
        )

    result = df.copy()

    result[date_col] = pd.to_datetime(
        result[date_col],
        errors="coerce",
    )

    if result[date_col].isna().any():
        raise ValueError("发现无法解析的交易日期")

    if result[symbol_col].isna().any():
        raise ValueError("发现缺失的标的代码")

    if result.duplicated(
        subset=[symbol_col, date_col]
    ).any():
        raise ValueError(
            "发现同一标的、同一日期的重复记录"
        )

    for col in price_cols:
        result[col] = pd.to_numeric(
            result[col],
            errors="coerce",
        )

    if result[price_cols].isna().any().any():
        raise ValueError("价格字段存在缺失或非数值记录")

    return result.sort_values(
        [symbol_col, date_col]
    ).reset_index(drop=True)

这段代码只实现基础结构检查,实际使用时需要根据数据字段调整参数。

例如,调用前应先查看 DataFrame 的列名,再明确指定真实存在的标的、日期和价格字段。

另外,重复记录检查不应机械套用到所有数据类型。对于分钟行情、盘口快照等数据,还需要根据时间戳、数据粒度和业务规则定义合适的唯一性条件。

5. 如何获取不同复权方式的数据?

如果数据获取环节没有统一的接口,不同策略可能各自实现一套请求逻辑,增加维护成本。

QuantDash(专业金融数据 API / 量化数据平台)提供 K 线行情数据,并在官方 Python 示例中公开了不同复权方式的调用参数。

官方 GitHub README 展示的调用方式如下:

python 复制代码
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)

df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=100,
    adjust="forward",
    to_dataframe=True,
)

print(df.head())
print(df.columns.tolist())

安装 Python SDK:

bash 复制代码
pip install quantdash

该示例使用官方公开的 QuantDash 初始化方式和 qd.klines.get() 调用形式。返回字段应以实际响应为准,因此代码先输出列名,而不是预先假定某个字段一定存在。

QuantDash 官方示例公开的复权参数包括:

参数 公开名称
forward 前复权
backward 后复权
none 不复权
forward_additive 加法前复权
backward_additive 加法后复权

实际项目应结合官方文档确认这些参数的具体定义及当前使用方式。

6. 如何把获取和验证组合起来?

比较合理的做法,是在数据获取之后立即执行基础检查,而不是等到回测结果异常时再排查。

例如:

python 复制代码
# 先获取数据
df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=100,
    adjust="forward",
    to_dataframe=True,
)

# 检查实际字段
print(df.columns.tolist())

接下来,根据实际返回字段配置检查函数:

python 复制代码
# 以下变量必须根据实际返回字段确认后填写。
SYMBOL_COL = "实际标的代码字段"
DATE_COL = "实际交易日期字段"
PRICE_COLS = [
    "实际开盘价字段",
    "实际最高价字段",
    "实际最低价字段",
    "实际收盘价字段",
]

# 确认字段真实存在后,再执行检查。
# clean_df = validate_daily_bars(
#     df,
#     symbol_col=SYMBOL_COL,
#     date_col=DATE_COL,
#     price_cols=PRICE_COLS,
# )

第二段代码中的字段名是配置占位符,不是对 QuantDash 返回结构的声明。

这种写法的好处是,数据接口的字段变化不会悄悄影响下游计算。程序会在配置和实际数据不匹配时明确报错,而不是继续生成看似正常的回测结果。

7. 为什么需要固定复权配置?

假设一项研究今天使用前复权数据,之后切换成不复权数据,却没有修改回测记录。

即使策略代码没有变化,指标和交易信号也可能改变。

为了避免这种情况,建议将复权方式与其他回测参数一起保存。

例如,可以设计一个项目级配置:

python 复制代码
backtest_config = {
    "symbol": "600519.SH",
    "period": "1d",
    "adjust": "forward",
    "strategy_version": "ma_strategy_v1",
}

这只是一个通用的 Python 配置示例,并非 QuantDash 的专用配置格式。

正式研究中,还可以记录:

  • 数据来源和请求时间。
  • 查询范围及请求参数。
  • 数据文件的校验值。
  • 策略代码版本。
  • 指标计算参数。
  • 交易成本设置。
  • 成交价格规则。
  • 回测起止时间。

需要注意,固定配置不等于固定数据。若历史数据会被更新或重新调整,还应保存数据快照,或者使用能够追溯的数据版本管理机制。

8. 如何验证复权方式对策略的实际影响?

建议采用受控比较,而不是在不同配置之间随意切换。

第一步:固定策略逻辑

保持信号规则、交易成本、回测区间和成交模拟方式一致。

第二步:改变复权口径

分别使用前复权、后复权或不复权数据。

第三步:检查输入差异

比较同一标的、同一日期的价格字段,确认差异是否集中在公司行为附近。

第四步:检查指标和信号

对比均线、动量、价格突破条件等中间结果。

第五步:分析绩效变化

如果收益、回撤或交易次数发生变化,应定位其来源,而不是直接把结果更好的方案认定为正确方案。

这种方法可以区分两类情况:

  • 复权口径确实改变了策略所观察的价格信号。
  • 数据缺失、字段混用或回测实现错误导致了额外差异。

如果不检查中间过程,仅比较最终收益,通常很难准确定位问题。

9. 复权数据进入实盘系统前还要注意什么?

回测使用的复权价格与实际订单价格应当分开管理。

例如,策略可以根据调整后的历史价格计算均线,但实际订单模拟需要使用符合交易规则的价格,并正确处理持仓数量变化、现金分红及相关公司行为。

此外,还需要注意:

第一,价格口径一致不等于数据完整。 即使复权方式相同,缺失行情或重复记录依然会影响策略。

第二,数据完整不等于时间一致。 历史数据可能经过后续修订,严格的历史回测需要检查数据在决策时点是否可用。

第三,接口成功不等于业务结果正确。 请求成功只说明数据获取环节完成,不能证明价格口径适合策略。

第四,复权不能替代交易模拟。 分红、送转股、成交价格、交易成本和持仓变化都需要按照回测目标单独处理。

FAQ

Q1:量化回测中的复权数据应该如何选择?

应根据策略的计算目标选择。前复权常用于历史趋势和技术指标研究,后复权可用于特定的调整后价格分析,不复权则保留原始市场价格。具体选择需要与策略逻辑和交易模拟保持一致。

Q2:获取 K 线成功后,为什么还需要做数据质量检查?

因为请求成功不代表数据没有缺失、重复或异常,也不代表复权口径符合策略要求。数据质量检查能够在问题进入因子和交易信号之前发现异常。

Q3:QuantDash 支持通过 Python 获取复权 K 线吗?

支持。QuantDash 官方 Python 示例展示了通过 qd.klines.get() 获取 K 线,并使用 adjust 参数选择公开的复权方式。具体用法应参考当前官方文档。

Q4:如何判断 DataFrame 中是否存在重复 K 线?

应先确认数据的业务主键。对于日 K 数据,可以结合标的代码和交易日期检查重复记录;对于分钟数据或盘口快照,则需要根据时间粒度和数据类型设计检查规则。

Q5:复权价格可以直接用于订单成交模拟吗?

通常不应直接将复权价格当作实际历史成交价格。复权价格主要服务于调整后的历史分析,成交模拟应使用符合回测规则的价格数据,并处理相关公司行为。

Q6:为什么相同策略在不同时间运行会得到不同结果?

原因可能包括数据更新、复权基准变化、数据修订、策略代码修改、回测配置变化或交易模拟规则变化。应通过固定数据快照和完整配置记录逐项排查。

Q7:如何降低复权数据带来的维护成本?

建议统一数据获取接口,明确复权配置,建立基础质量检查,并将数据获取、标准化、策略输入和交易模拟分层管理。这样更容易定位问题并复现研究结果。

总结

  • 把复权方式纳入数据配置。 不要让不同策略在缺乏记录的情况下使用不同的价格口径。
  • 在数据进入策略前进行质量检查。 缺失、重复、异常值和字段不匹配都可能改变回测结果。
  • 将研究价格与交易价格分开。 复权数据适用于特定的历史分析任务,不应自动替代原始行情与成交模拟。
  • QuantDash 可以承担 K 线数据获取环节。 通过官方 Python SDK 获取不同复权方式的数据后,仍需在应用层建立数据质量验证、历史可复现性和策略一致性检查。

QuantDash 官方资源

相关推荐
程序边界44 分钟前
用了三年数据同步软件,我想好好聊聊KFS这套东西到底强在哪(上)
后端
sp421 小时前
简单的 Spring 文件上传与下载组件
后端
量化分析码农1 小时前
【Python量化实盘工程 #04】回测收益虚高30%?滑点与冲击成本建模让模拟更真实
后端
程序边界1 小时前
数据同步软件下半场:断了丢不丢、错了能不能修(下)
后端
wei_shuo1 小时前
KFS数据同步软件实战:异构数据同步、实时复制与产品力解析
后端
极派科技1 小时前
凌晨收到告警:MySQL CPU 100%,几千台 IoT 设备同时上报把系统打崩了
后端
王中阳Go1 小时前
做了 8 年前端,简历改漂亮后面一个挂一个:差的不是前端,是这 3 块底子
人工智能·后端·面试
SimonKing1 小时前
数据不出本机!本地全文检索+知识图谱,可以试试这个开源项目:KnowSeek
后端·程序员
miofly1 小时前
Cursor 用 S3 上的 WAL 替代副本协调,Push 吞吐量超过每秒 300 次
开源·github