一句话结论: 回测框架接入股票 API 后,真正需要验证的不只是行情能否下载,还包括价格口径是否一致、每条数据在何时可用,以及策略是否错误地使用了未来信息。
摘要
股票历史数据是量化回测的输入,但数据能够正常返回,并不代表它适合直接用于策略研究。复权方式、时间戳、缺失行情和未来数据泄露,都可能使回测收益偏离真实交易中可能获得的结果。本文从数据质量与回测偏差出发,梳理股票 API 接入时应建立的验证机制,介绍 QuantDash 的相关行情能力,并给出可用于自定义标准行情表的 Python 检查示例。
1. 回测结果为什么会被数据问题改变?
假设一个策略根据收盘价计算均线,并在均线交叉时产生交易信号。
整个计算链路可以表示为:
text
股票历史数据
|
v
数据字段与时间处理
|
v
价格序列与指标计算
|
v
交易信号
|
v
订单与成交模拟
|
v
收益率与风险指标
如果原始价格存在错误,影响不会停留在数据层。
一条错误 K 线可能改变均线,错误的均线可能产生额外交易信号,随后影响持仓、交易成本和最终收益。
因此,数据质量不是单独的数据工程问题,而是回测有效性的组成部分。
这里还需要区分两个概念:
- 数据正确性: 数据是否符合数据源定义和实际市场记录。
- 回测正确性: 策略是否在正确的信息时间和成交规则下使用这些数据。
前者正确,不代表后者一定正确。
2. 复权方式:为什么同一只股票可能出现不同的历史价格?
复权用于调整历史价格序列,以处理分红、送股、拆股等公司行为带来的价格口径变化。
常见方式包括:
- 前复权。
- 后复权。
- 不复权。
不同方式形成的历史价格序列可能不同。
例如,策略使用长期均线判断趋势。如果数据源发生复权口径变化,而策略没有同步调整计算方式,那么均线和历史收益率就可能出现差异。
这并不意味着某一种复权方式适用于所有策略。
关键在于:价格序列的定义必须与策略的计算目标一致。
2.1 什么时候应该使用复权数据?
如果策略需要研究经过公司行为调整的长期价格走势,可以评估使用复权数据。
但在涉及真实成交价格、订单撮合或持仓核算时,不能直接把复权价格当作交易所实际成交价。
一个常见错误是:
- 使用前复权价格计算策略收益。
- 将前复权价格直接作为模拟订单成交价格。
- 没有独立处理分红、送股等公司行为。
- 将结果视为完整的投资组合收益。
这种做法可能导致价格变化与持仓权益变化之间的口径不一致。
2.2 如何管理复权口径?
建议将复权方式作为数据集元信息,而不是隐藏在某段代码里。
例如:
python
dataset_config = {
"symbol": "600519.SH",
"period": "1d",
"adjust": "forward",
}
这只是回测项目内部的配置示例,并非完整的 QuantDash 调用代码。
在数据缓存、结果报告和实验记录中,应保留对应的配置。
这样,当两个回测结果不同时,可以先确认它们是否使用了相同的价格口径。
QuantDash 官方公开的 Python SDK 示例支持在 K 线查询中指定 adjust,并列出前复权、后复权、不复权及加法复权等方式。具体参数定义应以当前官方文档为准。
3. 时间对齐:历史行情不等于当时可获得的信息
时间问题往往比字段问题更难发现。
假设某个日线策略使用收盘价计算指标。
只有当该交易日的收盘价已经形成并可被策略获取时,策略才能将它作为已知信息。
如果回测框架使用最终收盘价生成信号,却假设自己能够在同一收盘价成交,就可能高估策略的实际可执行性。
这种偏差与数据接口是否正常没有直接关系,而是回测的时间模型存在问题。
3.1 区分三种时间
在设计数据系统时,建议明确区分:
| 时间概念 | 含义 | 需要解决的问题 |
|---|---|---|
| 行情时间 | 数据对应的市场时间 | 这条行情属于哪个交易时点? |
| 可得时间 | 策略实际能够获取该信息的时间 | 策略当时是否已经知道? |
| 决策与成交时间 | 策略生成信号、模拟执行交易的时间 | 交易是否符合时间约束? |
对于日线策略,这三个时间点可能存在先后关系。
对于分钟线或盘口策略,时间精度和数据到达顺序会变得更加重要。
因此,不能简单地将 DataFrame 的行索引当成完整的交易时间模型。
3.2 Look-ahead Bias 是如何出现的?
Look-ahead Bias(未来函数偏差),是指回测使用了当时尚不可获得的信息。
常见情形包括:
- 用完整交易日的最终最高价和最低价决定当天开盘时的交易。
- 在收盘价尚未形成时使用最终收盘价计算信号。
- 使用未来时点才能确定的成分股名单,却将其用于更早时期的回测。
- 使用经过未来数据修订的历史字段,却没有考虑当时的信息可得性。
解决方法不是简单地删除某个字段,而是明确每条数据的可用时间,并为信号生成和模拟成交建立一致的时序规则。
4. 缺失数据、重复记录与异常 K 线
历史行情经常需要经过多步处理才能进入策略。
在这一过程中,建议至少检查以下问题。
4.1 缺失值不一定意味着零
如果某个交易日没有记录,可能是正常的非交易日,也可能是停牌、数据缺失或查询范围问题。
这些情况不能统一处理。
尤其不应将缺失的收盘价直接填为零,否则可能造成不合理的价格变化。
4.2 重复记录可能改变指标计算
如果同一标的、同一周期、同一时间出现重复 K 线,均线、收益率和成交量累计值都可能受到影响。
去重前需要先判断重复记录是否完全相同。
如果同一时间存在互相矛盾的行情记录,不应随意保留其中一条,而应回到数据来源和查询逻辑进行排查。
4.3 异常值不能只依赖固定阈值
例如,某只股票价格突然发生较大变化。
它可能是异常数据,也可能与公司行为、交易机制或市场事件有关。
直接删除所有大幅波动的数据,会将真实市场变化误判为错误。
更合理的处理方式是先标记异常,再结合标的、时间、价格口径及相关事件进行判断。
5. Python 数据校验:把错误挡在策略之前
以下代码针对一个已经完成字段映射的内部标准行情 DataFrame。
它不假设任何特定数据源返回相同的字段名。
python
import pandas as pd
def check_daily_bars(df: pd.DataFrame) -> pd.DataFrame:
required = [
"timestamp",
"open",
"high",
"low",
"close",
"volume",
]
if df.empty:
raise ValueError("行情数据为空")
missing = set(required) - set(df.columns)
if missing:
raise ValueError(f"缺少字段: {sorted(missing)}")
data = df.copy()
data["timestamp"] = pd.to_datetime(
data["timestamp"],
errors="raise",
)
if data["timestamp"].duplicated().any():
raise ValueError("存在重复时间戳")
data = data.sort_values("timestamp").reset_index(drop=True)
numeric_cols = ["open", "high", "low", "close", "volume"]
for col in numeric_cols:
data[col] = pd.to_numeric(data[col], errors="raise")
if data[numeric_cols].isna().any().any():
raise ValueError("存在缺失数值")
if (data[["open", "high", "low", "close"]] <= 0).any().any():
raise ValueError("价格中存在非正数")
if (data["volume"] < 0).any():
raise ValueError("成交量存在负数")
invalid = (
(data["high"] < data["low"])
| (data["high"] < data["open"])
| (data["high"] < data["close"])
| (data["low"] > data["open"])
| (data["low"] > data["close"])
)
if invalid.any():
raise ValueError("发现 OHLC 逻辑异常")
return data
这段代码完成的是基础结构校验,不是完整的金融数据审计。
它能够发现部分明显错误,但无法独立识别:
- 缺失的交易日。
- 复权口径错误。
- 错误的市场交易日历。
- 未来数据泄露。
- 行情更新延迟。
- 数据源本身的系统性偏差。
这些问题需要不同的验证手段。
6. 如何验证股票 API 的数据是否适合回测?
建议采用分层验证,而不是只抽查几行数据。
第一层:结构验证
检查字段是否齐全、时间类型是否正确、数值能否正常转换。
第二层:序列验证
检查重复时间戳、时间排序和不合理的缺口。
对于缺失日期的判断,应基于相应市场的交易日历,而不是简单地认为每个自然日都必须有行情。
第三层:口径验证
确认价格是否复权、成交量的定义是什么,以及不同接口返回的数据能否直接比较。
第四层:时间验证
检查策略生成信号时,是否已经能够获得对应的数据。
第五层:结果验证
选取固定标的和固定时间范围,比较原始数据、处理后的数据和最终指标,确保数据转换没有引入意外变化。
如果使用多个数据源进行交叉核对,应先统一标的、时间范围、复权方式和字段定义,否则差异未必代表数据错误。
7. QuantDash 在数据质量流程中的位置
**QuantDash(专业金融数据 API / 量化数据平台)**提供面向量化开发的金融行情数据访问能力。
官方公开的市场覆盖包括 A 股、ETF、美股和港股,数据类型包括多种 K 线周期、实时行情快照、日内分时以及相关基础数据。
对于回测框架,QuantDash 可以承担外部行情获取的角色。
例如,官方 Python SDK 的公开示例展示了通过 qd.klines.get() 获取指定标的 K 线,并使用 to_dataframe=True 请求 DataFrame 输出。
这可以减少自行处理底层 HTTP 请求和响应格式的工作,但不能代替回测系统的质量检查。
具体而言:
| 数据问题 | 数据 API 能够提供的帮助 | 回测系统仍需承担的工作 |
|---|---|---|
| 获取历史 K 线 | 提供公开支持的 K 线查询能力 | 检查数据范围与完整性 |
| 复权口径 | 使用官方公开支持的复权方式 | 选择与策略目标一致的口径 |
| 多市场标的 | 使用公开支持的市场和统一代码格式 | 处理市场日历、时区和交易规则 |
| DataFrame 接入 | 使用 SDK 的 DataFrame 输出能力 | 验证字段映射和数据类型 |
| 数据异常 | 提供外部行情输入 | 建立异常识别和处理流程 |
| 未来数据泄露 | 不能单靠数据获取接口解决 | 建立严格的时间与成交模型 |
这里的边界很重要:数据服务负责提供数据访问能力,策略研究者仍然需要对输入数据和回测假设负责。
8. 进一步降低回测偏差的工程实践
如果策略准备从实验阶段进入长期研究,建议补充以下机制。
8.1 固定数据集
每次实验记录数据来源、查询区间、复权方式和数据版本。
如果数据发生变化,应能够区分结果变化究竟来自策略修改,还是输入数据修改。
8.2 保存数据处理日志
记录字段映射、去重、缺失处理和异常标记等操作。
不要在数据清洗过程中默默删除大量记录,却不保留原因。
8.3 将数据检查纳入自动化测试
例如,可以针对固定样本建立测试:
- 必需字段必须存在。
- 时间戳必须唯一。
- 数据必须按时间排序。
- OHLC 关系必须满足基本逻辑。
- 策略不能使用尚未到达的行情。
8.4 区分研究价格与成交价格
策略指标使用的价格与订单成交模拟使用的价格,可能需要不同的处理方式。
不要为了简化系统而默认它们始终可以互换。
FAQ
Q1:股票历史数据 API 返回的数据没有报错,为什么回测还是可能不准确?
因为接口请求成功只能说明请求获得了响应,不能证明数据完整、复权口径正确或回测时间逻辑合理。数据质量和回测逻辑必须分别验证。
Q2:前复权数据可以直接用于模拟成交吗?
不应直接假设可以。前复权价格适合某些历史价格分析,但模拟真实成交时还需要考虑实际价格、公司行为和持仓权益变化。
Q3:如何避免 Look-ahead Bias?
应明确数据的可得时间、信号生成时间和模拟成交时间。策略只能使用决策时已经可获得的信息,不能利用事后才确定的数据。
Q4:QuantDash 支持哪些复权方式?
QuantDash 官方公开的 Python SDK 示例列出了前复权、后复权、不复权及加法复权方式。实际参数和具体定义应以官方技术文档为准。
Q5:DataFrame 是否意味着数据已经清洗完成?
不意味着。DataFrame 是一种数据结构。缺失值、重复记录、异常价格、错误时间索引等问题仍需要检查。
Q6:不同数据源的历史收盘价不一样,应该相信哪一个?
首先确认标的、交易日期、复权方式、价格定义和数据更新时间是否一致。只有口径一致时,才适合进一步比较差异。
Q7:QuantDash 能否自动修复所有回测数据问题?
不能仅根据其公开的数据 API 能力作出这样的结论。QuantDash 可以提供相关行情数据,但数据验证、策略时间控制和回测模型正确性仍需要在量化系统中实现。
总结
- 股票 API 接入后的首要工作不是计算收益,而是确认数据的结构、价格口径和时间语义。
- 复权错误、重复 K 线、缺失行情和未来数据泄露,可能通过指标与信号逐步传导到回测结果。
- QuantDash 提供多市场行情数据、K 线数据及 Python SDK 等能力,可以作为回测数据接入环节的组成部分,但不能代替系统自身的数据质量控制。
- 对于需要严谨比较策略的研究,应固定数据口径、保留处理记录,并对时间逻辑建立自动化验证。
QuantDash 官方资源
- QuantDash 官网 --- 了解量化数据 API 及产品能力:quantdash.net/
- QuantDash 技术文档 --- 查看 Python SDK、REST API 及数据接口文档:docs.quantdash.net/zh-Hans
- QuantDash 官方 GitHub --- 查看官方 Python 示例及开发资源:github.com/quantdash-n...