ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

回测结果不一致?可能是数据版本变了

回测结果不一致?可能是数据版本变了 半年前跑出来的, 回测曲线呈现出很好看的状态。今天把代码找出来, 参数没进行更改, 逻辑没做变动, 区间也没有调整, 然而结果却对不上。真正发生变化的, 有可能并非策略, 而是输入的数据: 复权的口径, 样本池的版本, 停牌的处理方式, 行情抓取的时间以及原始的响应, 任何一项没有留存版本, 旧的回测都极有可能无法再次实现。半年之前跑出去的回测曲线看着相当不错, 今日把代码找出来, 参数未曾变动, 逻辑未曾变动, 区间也未曾变动, 然而结果居然对不起来了。当时的第一反应是代码在哪个地方被改坏了, 在查询完提交记录之后, 发现策略代码的确没有变动, 就在这个时候, 你开始对自己产生怀疑, 怀疑自己是不是当时看错了, 是不是记错了回测参数。真正变化的往往不是策略而是输入。复权选用前复权还是后复权股票池是当时那个版本还是如今这个版本停牌以及退市要怎样进行处理行情数据是抓取的哪一天的今天再度下载同一时段的历史K线, 能否证实获取到的是同一份输入。旧回测要复现除了策略代码还得留下当时的数据入口证据。一、代码没变不等于输入没变在一段历史K线进入回测系统以前, 经历了一系列选择。多数人仅仅保存了策略代码以及回测结果, 然而, 处在数据输入链条上的关键环节, 诸如怎样请求的、返回了什么、有没有被清洗过, 通常都没有留存版本。层级你以为保存了什么实际还要保存什么策略层策略代码、参数、回测区间下单规则、滑点、手续费、调仓日、缺失值处理样本层股票列表样本池版本、成分生效日期、退市/停牌处理行情层OHLCV 数据接口、、、start/end、复权口径、抓取时间证据层数据能重新下载原始响应、 rows、hash、请求日志只保存策略代码等于只保存了回测的一部分。过了几个月后, 再次跑一回, 结果产生了变化, 你根本没办法判定这种变化究竟是源于策略, 还是源于数据, 又或者是源于样本, 亦或是源于某个清洗规则。排查的第一步不是怀疑策略失效而是先确认输入是否一致。二、用 拉一段 K 线建立复现基线这次进行的实测, 仅仅去做一件微小的事情, 那就是运用相关操作, 拉取.SH的一段日K, 并且保存请求参数, 保存原始响应, 保存hash。然后再次重复进行请求一次, 之后再去改变结束日期, 进而比较重叠区间。图注: 发出使用 GET /v1//kline 的请求, 针对的是.SH, 保持为1d, 最终返回为8, 且产生了本次K线的东西。这里的API Key已经做了脱敏处理。随后做了两类比较针对相同参数的重复请求, 去查看rows是不是一致对于固定起点、改变结束日期的情况, 去查看两个窗口的重叠区间是不是一致。注释为图, 两次做出请求, 参数相同, 所获取的hash保持一致针对exact以及与之相关的部分, 其重叠区间中的hash同样达成一致, 并且行数上存在差异的具体数量显示为0。检查项结果exact call 1OKexact call 2OKcallOKexact 行数同参数重复请求 rows一致exact/ 重叠区间行数exact/ 重叠区间差异行数rows这一步所具备的价值并非在于“拖拉至几根K线”, 而是在于, 针对这段既往的行情状况而言, 自请求参数起始, 历经返回摘要这一环节, 而后直至hash之处, 均已然存有可供追溯的证明依据了。往后的任何一个时刻, 当你要进行这次回测的复现之时, 你都能够首先就回答“输入是否保持一致”, 接着再去讨论“策略逻辑是不是需要进行复盘”。三、本次实测脚本里的关键代码下述内容, 是此次已然运行的脚本里的关键部分。它并非教学性质的伪代码, 而是用来生成上面终端截图、原始响应、rows以及.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_asciiFalse, sort_keysTrue, 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, }, methodGET, ) with urlopen(req, timeout20) as resp: body resp.read().decode(utf-8, errorsreplace) status resp.status payload json.loads(body) return { name: name, endpoint: path, params: params, http_status: status, payload_sha256: sha256_json(payload), payload: payload, }这段代码做了三件事紧固, 、、、这般的待请求参数留存请求反馈回来的原本的针对, 亦或是各行做, 留下痕迹。在回测复现当中, hash 的目的并非是为了让其看起来技术层面很复杂, 而是为了能够去减少彼此之间的争论。倘若存在两次这种情况, rows的hash呈现出一样的状态, 那么你起码能够先将“行情输入是否相同”这一事情朝着前进的方向推动一步要是hash并非一样, 那就接着去查看是具体哪几行、哪些字段出现了改变。四、旧回测复现前先留下这 6 件证据当进行回测复现的时候, 真正具备价值的并非是“我再次下载了一回 K 线”, 而是你是否能够予以回答:上次请求的参数究竟是啥, 上次抓取的时间到底是何时, 上次原始响应现今还存不存在, 此次重新抓取以后, 重叠区间属不属于一致, 要是不一致, 究竟是价格、成交量、时间戳, 还是字段结构出现了变动。在旧回测复现之前, 图注表明至少要留下接口参数, 要留下抓取时间, 要留下原始响应, 要留下响应哈希, 还要留下处理口径。许多团队会留存回测结果, 然而却不保留当时的数据进入口的证据。而后等到结果无法对上时就只能凭借印象去排查, 是复权的口径出现了变化吗, 是样本池发生了改变吗, 是交易日历有变动吗, 是某个清洗脚本产生了变化吗?这些问题均存有值得去进行查究的性质, 然而, 在开展排查的动作之前, 首先得存在一个最小基线, 那便是当时的数据输入到底是什么情况。五、改变结束日期为什么重叠区间也要查旧回测复现时常见操作是重新拉一段更长的历史数据。直感而言, 增添后续几日, 理应不会对先前已然重叠的日K造成改变。然而, 于研究系统当中, 绝不能仅仅依赖直感, 最优做法是将重叠区间提取出来, 逐段逐行加以比对。在此次实测当中, exact 的时间范围是从 2024 年 6 月 3 日开始, 一直到 2024 年 6 月 14 日结束。而另一个的时间范围则是从 2024 年 6 月 3 日起始, 直至 2024 年 6 月 21 日截止。这两者的重叠区间总共存在 8 根日 K, 它们之间的差异行数是 0, 并且 hash 是一致的。这个检查所具备的意义是, 在此次针对同一数据源进行的同一情况且同一状况且同一起点然而不同结束日期的查询当中, 重叠的区间并未被发现存在差异。下次进行复现回测之际, 要是这个检查未通过, 并且重叠区间的那数据起了变化, 此时你就会明白应当优先返回至数据输入层去展开排查, 而非一开始去怀疑策略代码。图注: 在进行旧回测复现这个行为的时候, 实际上真正需要去密切注视并且紧紧盯住的是数据输入这一层。请求参数、原始响应、抓取时间以及hash, 这些要素比“代码有没有发生变动”这个情况更早地暴露出问题。六、复权、PIT 和数据版本不要混成一件事回测结果变了很多人会立刻说“是不是有前视偏差”这可能对也可能不对。前视偏差, 也就是 PIT, 这是一个问题, 复权口径属于另一个问题, 数据版本留痕则是第三个问题。这几个问题常常会一同对回测造成影响, 然而却绝不能够混为一句话来说。问题关注点需要记录什么复权口径价格序列如何调整是否复权、前复权/后复权、不复权、复权因子版本PIT当时是否能知道这个信息财报发布时间、成分生效日、退市/停牌状态、数据可见时间数据版本这次输入是否和上次一致请求参数、抓取时间、原始响应、 hash样本池当时策略能交易哪些标的历史成分、筛选规则、剔除规则、幸存者偏差分清这几个问题下次回测结果对不上时排查路径才不会散掉。行情数据入口以及证据留痕的基础所能提供的是: 运用统一 API 拉取结构化行情, 分别保存请求参数、返回字段、还有原始响应以及 hash。复权口径、此外 PIT 判断以及样本池版本, 依旧需要和行情证据共同予以记录。七、 在这套流程里的位置若是仅仅手动瞧一下价格, 普通的行情软件便足够。但若得将行情数据接入研究脚本、回测管道、AI工具或者监控系统, 那就需要一个能够被程序稳定调用的、能够保存参数与响应的、能够被复核的数据入口。于这篇文章当中, 现身于回测复现链路、数据入口层的位置。它并非广告位, 而是工程流程里切实存在的一个环节。图注: 负责将行情数据, 通过统一接口, 以结构化字段的形式, 交给研究者请求参数、原始响应以及hash共同形成复现基线, 之后, 复现基线再与策略代码、样本池、复权/PIT规则一道, 进入回测归因。这是一种多市场实时行情数据 API, 它面向开发者, 也面向 AI Agent, 还面向量化研究者以及金融应用团队, 并且提供REST接入方式, 还提供MCP接入方式, 更提供Skill接入方式, 也提供CLI接入方式。它所具备的价值不单单是“查询一个价格”这般简单, 而是要将行情接入这一行为, 转化为能够进行记录的, 能够再次运行的, 能够成为放进工程流程里一部分的状态。八、旧回测复现检查卡察觉到下一回的旧回测没办法再跑回去了的这种情况发生的时候, 不要立刻急忙着急地去更改策略代码。而须按照这一张卡片进行一次排列:检查项为什么重要策略代码和参数是否有版本先确认策略逻辑有没有变化请求参数是否完整保存、、start/end、复权口径会改变输入抓取时间是否保存同一历史区间在不同抓取时间需要可追溯原始响应是否保存后续排查不能只看清洗后的结果rows 是否有 hash快速判断两次输入是否一致样本池/PIT/复权是否单独记录避免把所有变化都归因给“数据变了”不曾留下痕迹之际, 你仅能够去猜测。留有痕迹的时候, 存在两种情况: 假如输入发生了改变, 或者输入未曾改变然而策略结果产生了变化 , 你便能够依据有保留的痕迹而进行预先的判断。九、FAQQ1只保存回测结果和 CSV 文件不够吗是不够的状存在, CSV能够将一份数据结果予以保存, 然而却不一定能够把这份数据究竟是如何而来的情况说明清楚。对于要来予以复现的旧回测而言, 最优的做法是, 同时把请求参数、抓取时间、原始响应、字段和hash的形式保存下来。如此这般, 为的是有朝一日后续出现差异状况之际, 可供从源头起始展开查询而用。Q2同参数 hash 一致就说明回测一定能复现吗并非必然。其表明在此次检查期间的行情输入能够被调整至同一份rows。全过程完整回测复现仍需考量策略代码、手续费、滑点、复权、样本池、PIT、清洗规则以及执行逻辑。行情输入仅仅是最先需要确定的一个层面。Q3改变结束日期后重叠区间的数据会变吗
返回列表