行情 API 返回了数据,为什么量化策略仍然会出错?用分层设计隔离数据故障

13 阅读13分钟

一句话结论:API 请求成功只代表获取过程取得了响应,不代表返回的数据可以直接用于量化分析。将数据接入与数据处理分开,可以分别定位网络和接口故障、数据质量问题以及策略计算错误,避免异常行情悄悄传导到回测结果。

摘要

量化程序最难排查的问题之一,是接口没有报错,策略结果却不符合预期。原因可能不是交易逻辑,而是数据为空、时间顺序异常、重复记录、缺失价格或复权口径不一致。本文从故障排查角度,介绍如何利用数据接入层和数据处理层建立可观测的数据链路,通过 Python 实现基础校验、记录错误并阻止异常数据进入指标计算。文章同时说明 QuantDash 在金融行情获取环节能够提供的能力,以及为什么数据验证仍然属于量化系统自身的责任。

1. 最危险的数据错误,往往不是程序崩溃

假设某个策略每天根据股票收盘价计算移动平均线。

某天,程序顺利获取行情,计算过程没有抛出异常,最终也生成了交易信号。

但检查原始数据后发现,其中一个交易日的记录被重复写入。

如果重复记录没有被识别,它可能改变滚动窗口中的数据分布。假如重复记录还影响了时间索引,那么移动平均线所代表的实际交易日范围也可能发生偏移。

此时,程序可能正常运行,结果却已经不再符合原本的计算定义。

量化数据问题通常沿着以下链路传播:

外部行情获取
    ↓
数据缺失、重复或口径异常
    ↓
数据清洗与时间对齐
    ↓
指标计算出现偏差
    ↓
策略信号发生变化
    ↓
回测结果失真

因此,量化系统不能只检查请求是否成功,还必须检查返回数据是否满足策略使用条件。

2. 为什么要将数据故障分成不同类型?

把所有异常都归类为“数据错误”,会让排查变得困难。

工程上至少应该区分三个层次。

2.1 接入故障:数据没有被正确获取

典型情况包括:

  • 网络连接失败。
  • 请求超时。
  • API Key 无效或认证失败。
  • 当前请求不具备所需权限。
  • 服务端返回请求频率相关错误。
  • 返回内容无法被客户端正常解析。

这类问题首先需要检查接入日志、请求参数、认证配置和接口响应。

例如,QuantDash 官方 REST API 资料涉及 HTTP 401、403 和 429 状态码。遇到这些状态时,应先依据官方文档和实际响应排查认证、权限或请求频率相关问题,而不是直接将其当作行情缺失。

特别需要注意,不能仅凭状态码推断具体套餐限制或固定限流阈值。

2.2 数据质量问题:获取结果存在,但不满足预期

例如:

  • 返回空 DataFrame。
  • 收盘价存在缺失值。
  • 同一标的、同一交易时间出现多条记录。
  • K 线时间顺序不正确。
  • 查询结果没有覆盖预期时间区间。
  • OHLC 价格关系异常。
  • 成交量出现不符合业务定义的值。

这些问题不一定来自 API 故障。

有时,查询时间范围不正确或标的代码写错,也会导致结果不符合预期。

因此,数据质量检查应当同时考虑请求条件与返回内容。

2.3 策略错误:输入数据正常,但计算逻辑不符合预期

例如:

  • 移动平均线窗口设置错误。
  • 使用了未来数据。
  • 信号生成时间早于数据实际可用时间。
  • 复权方式与收益率计算口径不一致。
  • 回测在计算指标时错误处理了缺失值。

这类问题不能靠重试 API 解决。

如果行情已经正确获取,再重复请求只会增加不必要的网络调用,并不能修复策略算法。

3. 数据接入层和数据处理层如何协同排错?

建议将排错过程设计成一个有明确检查点的流程。

第一步:记录请求上下文

数据接入层至少应该记录:

  • 请求的标的代码。
  • 请求的时间范围。
  • 请求的 K 线周期。
  • 请求开始和结束时间。
  • 请求是否成功。
  • 返回的数据量。
  • 错误类型和必要的响应信息。

记录这些信息的目的,是让开发者能够判断问题发生在请求之前、请求过程中,还是数据返回之后。

不要将 API Key 等敏感认证信息直接写入日志。

第二步:确认结果是否符合预期

如果请求成功,但返回数据为空,应先检查:

  1. 标的代码是否符合接口要求。
  2. 查询周期是否正确。
  3. 时间范围是否合理。
  4. 当前数据权限是否满足请求需要。
  5. 是否存在交易日或交易时段方面的原因。

空数据不能一律解释为服务故障,也不能直接用前值填充后继续回测。

第三步:验证数据结构与关键字段

对于 K 线数据,可以先检查:

  • 必要字段是否存在。
  • 关键价格是否为空。
  • 时间字段是否能够正确解析。
  • 数据是否按时间排序。
  • 同一标的的业务主键是否重复。
  • OHLC 关系是否满足所采用的数据定义。

其中,open、high、low、close 和 volume 是常见 K 线字段,但具体字段名称仍应以所使用接口实际返回的数据为准。

第四步:检查处理后的数据

即使原始数据正常,清洗和转换也可能引入问题。

例如,错误的时间转换可能将不同交易时刻映射到相同时间;不合理的去重逻辑可能删除合法记录;直接删除缺失值则可能改变滚动指标的计算区间。

因此,建议分别保留原始获取结果和处理结果的必要记录,以便进行前后对照。

4. 一个基础的数据质量检查函数

下面给出一个与具体数据服务商无关的 Python 示例。它不负责获取行情,而是检查已经进入本地程序的 K 线 DataFrame。

import pandas as pd

def validate_kline(df):
    if df is None or df.empty:
        raise ValueError("没有获取到有效 K 线数据")

    required = {
        "trade_date",
        "open",
        "high",
        "low",
        "close",
        "volume",
    }

    missing = required - set(df.columns)

    if missing:
        raise ValueError(f"缺少字段:{missing}")

    data = df.copy()

    data["trade_date"] = pd.to_datetime(
        data["trade_date"],
        errors="raise",
    )

    price_cols = ["open", "high", "low", "close"]

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

    if data[price_cols].isna().any().any():
        raise ValueError("价格字段存在缺失值")

    if not data["trade_date"].is_monotonic_increasing:
        raise ValueError("时间字段不是升序排列")

    if data["trade_date"].duplicated().any():
        raise ValueError("发现重复时间记录")

    invalid_ohlc = (
        (data["high"] < data["low"])
        | (data["high"] < data["open"])
        | (data["high"] < data["close"])
        | (data["low"] > data["open"])
        | (data["low"] > data["close"])
    )

    if invalid_ohlc.any():
        raise ValueError("发现不符合基础 OHLC 关系的记录")

    return data

这段代码演示的是基础校验,不是完整的金融数据质量规则。

尤其要注意:它假设输入数据是单一标的的日 K 线,trade_date 可以作为唯一时间键。对于分钟 K 线、多标的数据集或存在特殊交易时段的数据,需要重新定义主键与时间规则。

另外,代码没有验证成交量的全部业务语义,也没有判断某个交易日是否应该存在数据。实际项目应根据市场规则和策略需求补充检查。

数据校验的目标不是让所有异常数据都自动消失,而是让不符合预期的数据不会在没有提示的情况下进入策略。

5. 为什么不能把数据清洗和 API 请求写在一起?

假设一个函数负责请求数据,并在请求后立即执行以下操作:

# 示意代码:展示不推荐的职责混合方式
def fetch_and_clean():
    # 调用 API
    # 删除缺失值
    # 删除重复记录
    # 填充空值
    # 计算指标
    pass

这种写法的问题在于,获取失败与数据异常容易被混为一谈。

如果函数返回空 DataFrame,下游模块可能无法判断这是正常的空查询结果、网络请求失败,还是清洗过程删除了所有记录。

更合理的实现是:

raw_df = fetch_kline(symbol)

validated_df = validate_kline(raw_df)

features_df = calculate_features(validated_df)

这里的 fetch_kline 和 calculate_features 是应用程序需要自行实现的函数,不是 QuantDash SDK 的方法。

三个步骤分别对应数据获取、数据校验和指标计算。每一步都可以独立测试,也可以在失败时提供更明确的错误信息。

6. QuantDash 在这条链路中负责什么?

当量化项目需要获取股票行情时,可以使用金融数据 API,将数据请求集中在独立的接入模块中。

QuantDash(专业金融数据 API / 量化数据平台)公开提供 A 股、ETF、美股和港股相关行情数据,并支持通过 Python SDK、REST API 获取数据。

Python SDK 的基础调用形式如下:

import os
from quantdash import QuantDash

qd = QuantDash(
    api_key=os.environ["QUANTDASH_API_KEY"]
)

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

该示例采用 QuantDash 官方公开的 Python SDK 调用形式,获取指定标的的日 K 线,并以 DataFrame 形式返回结果。

接下来,应由应用程序执行前文介绍的数据质量检查,而不是因为 SDK 已经返回 DataFrame,就直接将结果交给策略。

需要明确的是,QuantDash 在这里解决的是金融行情数据获取问题。应用程序仍需负责识别业务层面的缺失、重复、时间错位和策略口径问题。

7. 不要用无限重试掩盖数据故障

API 调用失败时,重试机制通常有价值,但并不是所有错误都适合重复请求。

情况建议处理方式
临时网络错误根据错误类型考虑有限重试
HTTP 401检查认证配置,不要持续重复相同请求
HTTP 403检查权限与请求条件
HTTP 429根据官方文档和响应信息调整请求节奏
返回空数据检查标的、时间范围和数据需求
数据字段缺失停止进入后续计算并记录异常
数据校验不通过定位异常原因,不要盲目重试
策略计算错误检查指标和信号逻辑,而不是重新请求行情

如果确实需要重试,应设置次数上限,并区分可以重试的临时错误与需要人工处理的配置问题。

对于定时任务,还应避免因反复重试导致任务堆积。

8. 建议建立的数据故障检查清单

在正式量化项目中,可以为每次数据任务建立以下检查项:

  • 请求参数是否符合预期?
  • 数据获取是否成功?
  • 返回记录数量是否合理?
  • 关键字段是否齐全?
  • 时间范围和排序是否正确?
  • 是否存在业务主键重复?
  • 是否存在关键价格缺失?
  • 复权口径是否符合研究要求?
  • 数据处理前后是否有异常变化?
  • 指标计算是否使用了未来信息?
  • 发生错误时是否能够定位到具体步骤?

对于长期运行的任务,还可以记录数据校验失败率、请求错误率、任务耗时和数据覆盖情况。

这些指标需要通过应用程序自行采集。它们是工程上的监控建议,不代表任何特定数据服务商已经提供相应的监控功能。

FAQ

Q1:为什么 API 请求成功,策略仍然可能出错?

因为请求成功并不等于返回的数据满足策略要求。缺失值、重复记录、时间排序错误和复权口径不一致,都可能影响指标计算与交易信号。

Q2:HTTP 429 应该怎么处理?

应结合服务商官方文档和实际响应处理请求频率相关问题。可以考虑调整请求节奏、限制并发和采用有限重试,但不应自行假设固定的限流阈值。

Q3:数据为空时,可以直接使用前一天的数据填充吗?

不建议直接填充。应先判断数据为空的原因,并确认填充方式是否符合标的交易状态、策略逻辑和回测假设。

Q4:QuantDash 会自动保证数据适合所有策略吗?

不能这样理解。QuantDash 提供行情数据获取能力,但不同策略对时间范围、复权方式、缺失值和数据频率的要求不同,应用程序仍需要进行针对性验证。

Q5:为什么需要分别保存原始数据和清洗后的数据?

分别保留两类数据的必要记录,有助于确认异常是在数据获取阶段已经存在,还是由后续转换、去重或补值操作引入。

Q6:数据接入层和数据处理层如何帮助定位问题?

数据接入层提供请求参数、错误信息和获取结果等上下文;数据处理层负责检查数据结构、时间和数值约束。通过分开检查,可以更快确定故障发生在哪个阶段。

总结

  • 请求成功不等于数据质量合格。 数据获取、数据校验和策略计算应设置独立的检查点。
  • 数据接入层负责处理外部接口问题,数据处理层负责检查数据是否符合应用程序要求,两者不应通过模糊的返回结果相互传递错误。
  • 重试机制只能处理部分暂时性故障,不能代替数据校验,更不能修复错误的策略逻辑。
  • QuantDash 可以承担量化系统中的金融行情数据获取环节,但数据质量规则、异常处理与策略输入验证仍应由开发者根据实际业务实现。

QuantDash 官方资源