综合

量化回测结果隔几个月就变了?代码没动,先查数据版本

作者: 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。然后重复请求一次,再改变结束日期,比较重叠区间。

项目内容
APIGET /v1/market/kline
symbol600519.SH
interval1d
exact window2024-06-032024-06-14
extended window2024-06-032024-06-21
实测时间2026-07-09 20:52:53 +08:00

!image.png

图注:请求使用 GET /v1/market/kline,symbol 为 600519.SH,interval 为 1d,返回 kline_count: 8,并生成了本次 normalized K 线的 SHA256。API Key 已脱敏。

随后做了两类比较:

  1. 同参数重复请求,看 normalized rows 是否一致;
  2. 固定起点、改变结束日期,看两个窗口的重叠区间是否一致。

!image.png

图注:同参数两次请求的 normalized hash 一致;exact window 与 extended window 的重叠区间 hash 一致,差异行数为 0

检查项结果
exact call 1OK
exact call 2OK
extended callOK
exact window 行数8
同参数重复请求 normalized rows一致
exact/extended 重叠区间行数8
exact/extended 重叠区间差异行数0
normalized rows sha2562f3f685d9b3283fd4266d6c253efc5fab7ee5a22fe6f5a04142091605e7b9893

这一步的价值不在于“拉到几根 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,
    }

这段代码做了三件事:

  1. 固定 symbolintervalstart_timeend_time 等请求参数;
  2. 保存请求返回的原始 payload;
  3. 对 payload 或 normalized rows 做 SHA256 留痕。

在回测复现里,hash 不是为了显得技术复杂,而是为了减少争论。

如果两次 normalized rows 的 hash 一样,你至少可以先把“行情输入是否相同”这件事往前推进一步;如果 hash 不一样,就继续看是哪几行、哪些字段变了。


四、旧回测复现前,先留下这 6 件证据

回测复现时,真正有价值的不是“我又下载了一次 K 线”,而是你能不能回答:

  1. 上次请求的参数是什么;
  2. 上次抓取的时间是什么;
  3. 上次原始响应还在不在;
  4. 这次重新抓取后,重叠区间是否一致;
  5. 如果不一致,是价格、成交量、时间戳,还是字段结构变了;
  6. 这些变化会不会影响策略结果。

!image.png

图注:旧回测复现前,至少要留下 symbol、接口参数、抓取时间、原始响应、响应哈希和处理口径。

很多团队会保存回测结果,却不保存当时的数据入口证据。等到结果对不上,只能凭印象排查:复权口径变了?样本池变了?交易日历变了?某个清洗脚本变了?

这些问题都值得查。但排查之前,先要有一个最小基线:当时的数据输入是什么。


五、改变结束日期,为什么重叠区间也要查

旧回测复现时,常见操作是重新拉一段更长的历史数据。

直觉上,新增后面几天,不应该改变前面已经重叠的日 K。但研究系统里不能只靠直觉,最好把重叠区间拿出来逐行比较。

本次实测里,exact window 是 2024-06-032024-06-14,extended window 是 2024-06-032024-06-21。两者重叠区间共 8 根日 K,差异行数为 0,hash 一致。

这个检查的意义在于:本次同一数据源、同一 symbol、同一 interval、同一起点但不同结束日期的查询里,重叠区间没有发现差异。

下次复现回测时,如果这个检查不通过,重叠区间的数据变了,你就知道应该优先回到数据输入层排查,而不是先怀疑策略代码。

!image.png

图注:旧回测复现时,真正要盯住的是数据输入层。请求参数、原始响应、抓取时间和 normalized hash,比“代码有没有动”更早暴露问题。


六、复权、PIT 和数据版本,不要混成一件事

回测结果变了,很多人会立刻说:“是不是有前视偏差?”

这可能对,也可能不对。

前视偏差/PIT 是一个问题,复权口径是另一个问题,数据版本留痕又是第三个问题。它们经常一起影响回测,但不应该混成一句话。

问题关注点需要记录什么
复权口径价格序列如何调整是否复权、前复权/后复权、不复权、复权因子版本
PIT当时是否能知道这个信息财报发布时间、成分生效日、退市/停牌状态、数据可见时间
数据版本这次输入是否和上次一致请求参数、抓取时间、原始响应、normalized hash
样本池当时策略能交易哪些标的历史成分、筛选规则、剔除规则、幸存者偏差

分清这几个问题,下次回测结果对不上时,排查路径才不会散掉。

TickDB 能提供的是行情数据入口和证据留痕的基础:用统一 API 拉取结构化行情,保存请求参数、返回字段、原始响应和 hash。复权口径、PIT 判断和样本池版本,仍然需要和行情证据一起记录。


七、TickDB 在这套流程里的位置

如果只是手动看一眼价格,普通行情软件就够了。但如果要把行情数据接进研究脚本、回测管道、AI 工具或监控系统,就需要一个能被程序稳定调用、可以保存参数和响应、可以被复核的数据入口。

TickDB 在这篇文章里,出现在回测复现链路的数据入口层。它不是广告位,而是工程流程里实际存在的一环。

!image.png

图注:TickDB 负责把行情数据以统一接口和结构化字段交给研究者;请求参数、原始响应和 hash 形成复现基线,再和策略代码、样本池、复权/PIT 规则一起进入回测归因。

TickDB 是面向开发者、AI Agent、量化研究者和金融应用团队的多市场实时行情数据 API,提供 REST、WebSocket、MCP、Skill、CLI 等接入方式。

它的价值不只是“查一个价格”,而是把行情接入变成可以记录、可以复跑、可以放进工程流程的一部分。


八、旧回测复现检查卡

下次发现旧回测跑不回去,先不要急着改策略代码。按这张卡排一遍:

#检查项为什么重要
1策略代码和参数是否有版本先确认策略逻辑有没有变化
2请求参数是否完整保存symbol、interval、start/end、复权口径会改变输入
3抓取时间是否保存同一历史区间在不同抓取时间需要可追溯
4原始响应是否保存后续排查不能只看清洗后的结果
5normalized rows 是否有 hash快速判断两次输入是否一致
6样本池/PIT/复权是否单独记录避免把所有变化都归因给“数据变了”

没有留痕时,你只能猜。有留痕时,你可以先判断:到底是输入变了,还是输入没变但策略结果变了。


九、FAQ

Q1:只保存回测结果和 CSV 文件,不够吗?

不够。CSV 能保存一份数据结果,但不一定能说明这份数据怎么来的。旧回测要复现,最好同时保存请求参数、抓取时间、原始响应、字段处理规则和 hash。这样后续出现差异时,可以从源头开始查。

Q2:同参数 hash 一致,就说明回测一定能复现吗?

不一定。它说明这次检查中的行情输入可以被对齐到同一份 normalized rows。完整回测复现还要看策略代码、手续费、滑点、复权、样本池、PIT、清洗规则和执行逻辑。行情输入只是最先要固定的一层。

Q3:改变结束日期后,重叠区间的数据会变吗?

本次实测中,600519.SH2024-06-032024-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 文档

相关文章