news 2026/9/29 9:45:30

分钟K线时区契约与午休处理:tick-stock-panel 分钟回测不出错的 3 个关键细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分钟K线时区契约与午休处理:tick-stock-panel 分钟回测不出错的 3 个关键细节

分钟K线时区契约与午休处理:tick-stock-panel 分钟回测不出错的 3 个关键细节

【免费下载链接】tick-stock-panelTSP自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台 | LLM能力驱使策略定制+个股分析+复盘 | 自由接入第三方数据源与个性化扩展数据 | 个人开源项目地址: https://gitcode.com/GitHub_Trending/ti/tick-stock-panel

tick-stock-panel 是一款自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台。很多人做分钟级回测时踩过同一个坑:数据源给的是 UTC 时间戳、服务器容器默认时区又是 UTC,再叠加 A 股特有的午休缺口,分钟K线时间对不齐,回测信号就悄悄错了。这篇文章带你拆解 tick-stock-panel 如何用一条「时区契约」和一套「午休口径」,把这类错误挡在数据入库之前。

问题从哪来:Docker 里默认时区是 UTC

A 股连续竞价时段是北京时间 9:30-11:30 与 13:00-15:00。而常见的python:slim镜像默认时区是 UTC——北京的 9:15-15:05,换算成 UTC 只有 1:15-7:05。

如果轮询、落盘用服务器本地时间判断"现在是不是交易时段",Docker 部署下窗口会和真实交易时段完全错开。tick-stock-panel 的解法是不依赖系统时区,固定使用北京时间常量,所有时段判断都走 market_time.py:

  • CN_TZ = UTC+8(无夏令时,不会漂移)
  • 上午 120 分钟 + 下午 120 分钟 = 全天 240 分钟
  • 已交易分钟数在盘中实时累计,收盘后封顶 240

时区契约:分钟K线统一为"北京墙钟"

项目的核心约定写在 CONTRIBUTING.md 的「日期、交易日和时区」一节(§3.3):

kline_minute.datetime必须是北京时间墙钟(naive,不带时区),如09:35:00。

围绕这条契约,kline_sync.py 实现了守卫函数_enforce_minute_beijing_wallclock,在两个源头入口强制执行:TickFlow 数据帧和插件/自定义数据源帧。它的处理规则可以概括为一张表:

输入形态守卫行为
带时区(tz-aware)转换为 Asia/Shanghai 后去掉时区标记
无时区,时刻落在交易时段(9/10/11/13/14/15 点)判定为北京墙钟,直接放行
无时区,整体呈"交易时段 -8 小时"的 UTC 特征(1/2/3/5/6/7 点)自动 +8 小时纠偏,并记录日志
无法识别的口径拒收:抛出异常,绝不让脏时间入库

几个设计细节值得新手学习:

  • 先显式转换再守卫:TickFlow 的毫秒时间戳按 UTC 基准,_normalize_minute 会先换算成北京墙钟再进守卫,01:30 UTC一定变成09:30入库;
  • 幂等:纠偏后的帧再次通过守卫会直接放行,不会二次 +8;
  • fail-closed:宁可报错拒收,也不猜一个"看起来合理"的时间写进库。

对应测试 test_minute_timezone_contract.py 覆盖了显式转换、UTC 特征自愈、混合口径、拒收等全部分支,是理解契约的最佳阅读材料。

自定义数据源:契约违规就回退,不硬扛

tick-stock-panel 支持自由接入第三方数据源与插件。自定义源返回的分钟帧同样要过守卫(见 kline_sync.py 的_try_custom_minute):

  • 插件返回 UTC 墙钟 → 守卫自动 +8 纠偏后正常下发;
  • 插件返回无法识别的时间口径 → 直接回退到 TickFlow 主数据源。

对自托管用户来说,这意味着接一个新数据源时不需要自己操心时区换算——要么被自动纠偏,要么被安全回退,本地库里的分钟K线始终保持同一种口径。

午休处理:11:31-12:00 不该多出来的 30 分钟

A 股午间休市(11:30-13:00)是分钟数据的第二个暗坑。前端分时图会按当前时刻估算"今天应有多少根分钟K",本地数据够了就用本地,不够才去实时补拉。

曾经的实现里,上午分支写成h < 12 or (h == 12 and m == 0),导致11:31-12:00 期间期望根数被继续累加到 121~150 根,而实际上午最多只有 120 根(09:31-11:30)。后果是本地明明完整的上午数据被误判为"不完整":

  • 个股分时图:触发实时拉取,拉空后响应为空,轮询直接停止,分时图空白直到重新打开;
  • 自选列表分时:被归为"尾部落后",每轮都对所有自选标的发起增量补拉,白白消耗限流配额。

修复后的口径与 market_time.py 的已交易分钟数完全一致:午休期间期望值保持上午累计的 120 根封顶,见 kline.py:

  • 开盘前(9:30 之前)→ 0
  • 上午时段 → 0~120 随时间增长
  • 11:30-13:00 午休 → 恒定 120
  • 下午时段 → 120~240 随时间增长
  • 收盘后 / 历史交易日 → 240

数据完整性检测也把午休当作合法结构:相邻K线的间隔要么是 1 分钟,要么是午休缺口91 分钟(11:30 → 13:01),见 kline.py 中的_LUNCH_GAP_MIN = 91。其他任何间隔都视为缺洞,触发全天重拉回填。

回归测试 test_kline_minute_lunch_expected.py 把 11:35、11:50、12:00 三个午休时刻全部钉死为"本地 120 根、不触发拉取",防止这个 bug 复活。

这些细节如何保证分钟回测正确

分钟回测的正确性建立在一条链路上:

  1. 入口收口——所有数据源(官方 + 自定义插件)在源头统一为北京墙钟,脏数据 fail-closed 拒收;
  2. 口径一致——已交易分钟数、分时期望根数、午休缺口识别共用同一套 240 分钟模型,前端口径与回测口径不打架;
  3. 测试兜底——时区契约、午休期望、缺口检测各有专项测试,口径变更会立刻被回归抓住。

对新手来说,这套"契约 + 守卫 + 测试"的组合拳是处理时间敏感金融数据的通用范式:不要在各处各自换算时区,而是定一条全库契约,在边界处强制校验。

关键源码与文档索引

  • 时区工具与交易时段模型:backend/app/market_time.py
  • 分钟K归一化与时区守卫:backend/app/services/kline_sync.py
  • 分时/自选分时期望根数与午休缺口检测:backend/app/api/kline.py
  • 时区契约回归测试:backend/tests/test_minute_timezone_contract.py
  • 午休期望根数回归测试:backend/tests/test_kline_minute_lunch_expected.py
  • 契约原文:CONTRIBUTING.md §3.3「日期、交易日和时区」

掌握这两组细节,你再接入任何分钟级数据源,都能先问对两个问题:时间是什么口径?午休缺口放不放心?

【免费下载链接】tick-stock-panelTSP自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台 | LLM能力驱使策略定制+个股分析+复盘 | 自由接入第三方数据源与个性化扩展数据 | 个人开源项目地址: https://gitcode.com/GitHub_Trending/ti/tick-stock-panel

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 9:42:59

Java图书管理系统源码实战:从环境搭建到二次开发全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 9:42:55

可调LDO输出噪声降噪:RC网络、前馈电容与噪声增益计算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 9:36:44

Superpowers实战:给Codex装上上下文管理与任务清单,搞定Java复杂重构

1. 一场深夜排障让我重新认识了"superpowers"这个词大约两个月前的一个周五晚上&#xff0c;我被一个让人抓狂的Java编译错误困住了。错误信息指向一个泛型方法的重载冲突&#xff0c;IntelliJ的代码提示又一直跟我绕圈子——它建议的类型参数和实际传入的参数类型总…

作者头像 李华
网站建设 2026/9/29 9:35:54

从零搭建可复现AI工程:数据清洗、Prompt与Agent实践

我一直觉得&#xff0c;“AI工程”听起来特别高大上&#xff0c;但实际上它离我们普通开发者的日常特别近。今天想聊的&#xff0c;就是这个从零开始的完整过程&#xff1a;怎么从只会写几个Python脚本&#xff0c;到能独立搭出一个有数据、有模型、有测试、能上线的AI工程。我…

作者头像 李华
网站建设 2026/9/29 9:35:42

电脑自动重启故障排查:软硬协同诊断指南

简介&#xff1a;本资源是一份面向IT运维人员、计算机爱好者及初级硬件工程师的实用故障排查指南&#xff0c;聚焦电脑自动重启这一高频疑难问题&#xff0c;系统梳理软硬件双维度成因与应对策略。文档以PDF格式单文件交付&#xff08;15KB&#xff09;&#xff0c;内容结构清晰…

作者头像 李华
网站建设 2026/9/29 9:34:55

IDEA中Git操作:Fetch/Pull/Update Project区别

每天都有同事在 IDEA 里点那几个 Git 按钮&#xff0c;然后转头问我&#xff1a;"Fetch、Pull、Update Project 到底有什么区别&#xff1f;我按哪个才是拉代码&#xff1f;"说实话&#xff0c;这三个操作看着像&#xff0c;实际做的事完全不在一个层面上。尤其是 Up…

作者头像 李华