量化回测结果隔几个月就变了?代码没动,先查数据版本
作者: TickDB Research · 发布: 2026/7/10 · 阅读: 8
标签: QL-OLD-004, 知乎A009
量化回测结果隔几个月就变了?代码没动,先查数据版本
半年前跑出来的回测曲线很好看。今天把代码翻出来,参数没改,逻辑没改,区间也没改,但结果就是对不上。真正变化的,可能不是策略,而是输入数据:复权口径、样本池版本、停牌处理、行情抓取时间和原始响应,任何一项没留版本,旧回测都可能复现不了。
半年前跑出来的回测曲线很好看。今天把代码翻出来,参数没改,逻辑没改,区间也没改,但结果就是对不上。
第一反应是代码哪里改坏了。查完提交记录,策略代码确实没动。这时候你开始怀疑自己:是不是当时看错了?是不是记错了回测参数?
真正变化的,往往不是策略,而是输入。
复权用的是前复权还是后复权?股票池是当时的版本还是今天的版本?停牌和退市怎么处理?行情数据是哪一天抓的?今天重新下载同一段历史 K 线,能不能证明拿到的是同一份输入?
旧回测要复现,除了策略代码,还得留下当时的数据入口证据。
一、代码没变,不等于输入没变
一段历史 K 线进入回测系统之前,经过了一串选择。大多数人只保存了策略代码和回测结果,但数据输入链条上的关键环节:怎么请求的、返回了什么、有没有被清洗过,往往没有留版本。
| 层级 | 你以为保存了什么 | 实际还要保存什么 |
|---|---|---|
| 策略层 | 策略代码、参数、回测区间 | 下单规则、滑点、手续费、调仓日、缺失值处理 |
| 样本层 | 股票列表 | 样本池版本、成分生效日期、退市/停牌处理 |
| 行情层 | OHLCV 数据 | 接口、symbol、interval、start/end、复权口径、抓取时间 |
| 证据层 | 数据能重新下载 | 原始响应、normalized rows、hash、请求日志 |
只保存策略代码,等于只保存了回测的一部分。
几个月后重新跑一次,结果变了,你没法判断变化来自策略、数据、样本,还是某个清洗规则。
排查的第一步,不是怀疑策略失效,而是先确认输入是否一致。
二、用 TickDB 拉一段 K 线,建立复现基线
这次实测只做一件小事:用 TickDB 拉取 600519.SH 的一段日 K,保存请求参数、原始响应和 normalized hash。然后重复请求一次,再改变结束日期,比较重叠区间。
| 项目 | 内容 |
|---|---|
| API | GET /v1/market/kline |
| symbol | 600519.SH |
| interval | 1d |
| exact window | 2024-06-03 至 2024-06-14 |
| extended window | 2024-06-03 至 2024-06-21 |
| 实测时间 | 2026-07-09 20:52:53 +08:00 |
图注:请求使用 GET /v1/market/kline,symbol 为 600519.SH,interval 为 1d,返回 kline_count: 8,并生成了本次 normalized K 线的 SHA256。API Key 已脱敏。
随后做了两类比较:
- 同参数重复请求,看 normalized rows 是否一致;
- 固定起点、改变结束日期,看两个窗口的重叠区间是否一致。
图注:同参数两次请求的 normalized hash 一致;exact window 与 extended window 的重叠区间 hash 一致,差异行数为 0。
| 检查项 | 结果 |
|---|---|
| exact call 1 | OK |
| exact call 2 | OK |
| extended call | OK |
| exact window 行数 | 8 |
| 同参数重复请求 normalized rows | 一致 |
| exact/extended 重叠区间行数 | 8 |
| exact/extended 重叠区间差异行数 | 0 |
| normalized rows sha256 | 2f3f685d9b3283fd4266d6c253efc5fab7ee5a22fe6f5a04142091605e7b9893 |
这一步的价值不在于“拉到几根 K 线”,而在于:这段历史行情从请求参数到返回摘要,再到 normalized hash,都有了可追溯证据。
以后任何时候复现这次回测,你都能先回答“输入是否一致”,再讨论“策略逻辑是否需要复盘”。
三、本次实测脚本里的关键代码
下面是本次已运行脚本中的关键片段。它不是教学伪代码,而是用于生成上面终端截图、原始响应、normalized rows 和 evidence_summary.json 的真实脚本片段。
BASE_URL = "https://api.tickdb.ai"
SYMBOL = "600519.SH"
INTERVAL = "1d"
ASSET_TYPE = "stock"
WINDOW_START = "2024-06-03"
WINDOW_EXACT_END = "2024-06-14"
WINDOW_EXTENDED_END = "2024-06-21"
def sha256_json(payload):
data = json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
return hashlib.sha256(data).hexdigest()
def request_json(name, path, params, key):
query = urlencode(params)
url = f"{BASE_URL}{path}?{query}"
req = Request(
url,
headers={
"X-API-Key": key,
"Accept": "application/json",
"User-Agent": "TickDB-QL-OLD-004-evidence/1.0",
},
method="GET",
)
with urlopen(req, timeout=20) as resp:
body = resp.read().decode("utf-8", errors="replace")
status = resp.status
payload = json.loads(body)
return {
"name": name,
"endpoint": path,
"params": params,
"http_status": status,
"payload_sha256": sha256_json(payload),
"payload": payload,
}
这段代码做了三件事:
- 固定
symbol、interval、start_time、end_time等请求参数; - 保存请求返回的原始 payload;
- 对 payload 或 normalized rows 做 SHA256 留痕。
在回测复现里,hash 不是为了显得技术复杂,而是为了减少争论。
如果两次 normalized rows 的 hash 一样,你至少可以先把“行情输入是否相同”这件事往前推进一步;如果 hash 不一样,就继续看是哪几行、哪些字段变了。
四、旧回测复现前,先留下这 6 件证据
回测复现时,真正有价值的不是“我又下载了一次 K 线”,而是你能不能回答:
- 上次请求的参数是什么;
- 上次抓取的时间是什么;
- 上次原始响应还在不在;
- 这次重新抓取后,重叠区间是否一致;
- 如果不一致,是价格、成交量、时间戳,还是字段结构变了;
- 这些变化会不会影响策略结果。
图注:旧回测复现前,至少要留下 symbol、接口参数、抓取时间、原始响应、响应哈希和处理口径。
很多团队会保存回测结果,却不保存当时的数据入口证据。等到结果对不上,只能凭印象排查:复权口径变了?样本池变了?交易日历变了?某个清洗脚本变了?
这些问题都值得查。但排查之前,先要有一个最小基线:当时的数据输入是什么。
五、改变结束日期,为什么重叠区间也要查
旧回测复现时,常见操作是重新拉一段更长的历史数据。
直觉上,新增后面几天,不应该改变前面已经重叠的日 K。但研究系统里不能只靠直觉,最好把重叠区间拿出来逐行比较。
本次实测里,exact window 是 2024-06-03 至 2024-06-14,extended window 是 2024-06-03 至 2024-06-21。两者重叠区间共 8 根日 K,差异行数为 0,hash 一致。
这个检查的意义在于:本次同一数据源、同一 symbol、同一 interval、同一起点但不同结束日期的查询里,重叠区间没有发现差异。
下次复现回测时,如果这个检查不通过,重叠区间的数据变了,你就知道应该优先回到数据输入层排查,而不是先怀疑策略代码。
图注:旧回测复现时,真正要盯住的是数据输入层。请求参数、原始响应、抓取时间和 normalized hash,比“代码有没有动”更早暴露问题。
六、复权、PIT 和数据版本,不要混成一件事
回测结果变了,很多人会立刻说:“是不是有前视偏差?”
这可能对,也可能不对。
前视偏差/PIT 是一个问题,复权口径是另一个问题,数据版本留痕又是第三个问题。它们经常一起影响回测,但不应该混成一句话。
| 问题 | 关注点 | 需要记录什么 |
|---|---|---|
| 复权口径 | 价格序列如何调整 | 是否复权、前复权/后复权、不复权、复权因子版本 |
| PIT | 当时是否能知道这个信息 | 财报发布时间、成分生效日、退市/停牌状态、数据可见时间 |
| 数据版本 | 这次输入是否和上次一致 | 请求参数、抓取时间、原始响应、normalized hash |
| 样本池 | 当时策略能交易哪些标的 | 历史成分、筛选规则、剔除规则、幸存者偏差 |
分清这几个问题,下次回测结果对不上时,排查路径才不会散掉。
TickDB 能提供的是行情数据入口和证据留痕的基础:用统一 API 拉取结构化行情,保存请求参数、返回字段、原始响应和 hash。复权口径、PIT 判断和样本池版本,仍然需要和行情证据一起记录。
七、TickDB 在这套流程里的位置
如果只是手动看一眼价格,普通行情软件就够了。但如果要把行情数据接进研究脚本、回测管道、AI 工具或监控系统,就需要一个能被程序稳定调用、可以保存参数和响应、可以被复核的数据入口。
TickDB 在这篇文章里,出现在回测复现链路的数据入口层。它不是广告位,而是工程流程里实际存在的一环。
图注:TickDB 负责把行情数据以统一接口和结构化字段交给研究者;请求参数、原始响应和 hash 形成复现基线,再和策略代码、样本池、复权/PIT 规则一起进入回测归因。
TickDB 是面向开发者、AI Agent、量化研究者和金融应用团队的多市场实时行情数据 API,提供 REST、WebSocket、MCP、Skill、CLI 等接入方式。
它的价值不只是“查一个价格”,而是把行情接入变成可以记录、可以复跑、可以放进工程流程的一部分。
八、旧回测复现检查卡
下次发现旧回测跑不回去,先不要急着改策略代码。按这张卡排一遍:
| # | 检查项 | 为什么重要 |
|---|---|---|
| 1 | 策略代码和参数是否有版本 | 先确认策略逻辑有没有变化 |
| 2 | 请求参数是否完整保存 | symbol、interval、start/end、复权口径会改变输入 |
| 3 | 抓取时间是否保存 | 同一历史区间在不同抓取时间需要可追溯 |
| 4 | 原始响应是否保存 | 后续排查不能只看清洗后的结果 |
| 5 | normalized rows 是否有 hash | 快速判断两次输入是否一致 |
| 6 | 样本池/PIT/复权是否单独记录 | 避免把所有变化都归因给“数据变了” |
没有留痕时,你只能猜。有留痕时,你可以先判断:到底是输入变了,还是输入没变但策略结果变了。
九、FAQ
Q1:只保存回测结果和 CSV 文件,不够吗?
不够。CSV 能保存一份数据结果,但不一定能说明这份数据怎么来的。旧回测要复现,最好同时保存请求参数、抓取时间、原始响应、字段处理规则和 hash。这样后续出现差异时,可以从源头开始查。
Q2:同参数 hash 一致,就说明回测一定能复现吗?
不一定。它说明这次检查中的行情输入可以被对齐到同一份 normalized rows。完整回测复现还要看策略代码、手续费、滑点、复权、样本池、PIT、清洗规则和执行逻辑。行情输入只是最先要固定的一层。
Q3:改变结束日期后,重叠区间的数据会变吗?
本次实测中,600519.SH 在 2024-06-03 至 2024-06-14 这个窗口内,改变结束日期后重叠区间数据没有变化。但不同数据源、不同品种、不同周期的表现可能不同,你的回测窗口需要用自己的数据独立验证。
Q4:个人研究员也需要这么复杂的留痕吗?
需要,但可以轻量化。哪怕你只保存一份请求参数 JSON、一个原始响应文件、一个 normalized hash,也比只保存最终回测曲线可靠得多。真正出问题时,这些证据能帮你少走很多弯路。
Q5:为什么这篇文章用 TickDB 的 REST K 线接口?
因为本文讨论的是历史行情输入和回测复现。REST 更适合一次性查询、脚本取数、历史 K 线拉取、字段核对和落库。实时监控或行情面板持续推送,更适合用 WebSocket。
Q6:TickDB 能不能用于 AI 工具取数?
可以。TickDB 支持 REST、WebSocket、MCP、Skill、CLI 等接入方式。对于 AI 工具场景,MCP 或 Skill 可以让 AI 助手通过工具调用读取行情,而不是靠模型记忆编一个价格。本文展示的是 REST 层的真实数据调用逻辑。
Q7:TickDB 在多市场场景里有什么用?
多市场最容易乱在 symbol、字段、时间戳和接口形态。TickDB 用统一 symbol 规则和结构化 API 承接外汇、贵金属、指数、美股、港股、A 股、加密货币等市场,更适合写跨市场研究脚本、行情面板和 AI 工具调用。
旧回测复现不了时,不要只问“策略是不是失效了”。先问:当时那份行情输入,今天还能不能被还原、被比对、被解释?
如果答案是不能,那就先补数据版本留痕。
本文内容仅讨论行情数据留痕、回测复现和数据版本治理方法,不构成任何投资建议。
通过 TickDB API 获取实时行情数据
一个 API 接入外汇、加密货币、美股、港股、A股、贵金属和全球指数的实时行情。支持 WebSocket 低延迟推送,免费开始使用。
免费领取 API Key查看 API 文档