这是一份写给 QMT(迅投极速策略交易系统)个人用户 的交易日判断实战指南。
全文分四块:先讲清原理(为什么”判断交易日”比你想的要多想一层)、再给实现思路与关键代码片段(核心逻辑不长,把思路讲透,剩下的自己搭)、然后讲两种典型用法(策略启动时判断 + 定时任务)、最后给排错表和完整免责声明。
代码片段基于 QMT 内置 Python 3.6,面向实盘策略使用。
一、这份指南给你什么
先按你的情况,挑一条阅读路径:
| 你是谁 | 建议路径 |
|---|---|
| 刚接触 QMT | 从 §2 读起,搞懂”为什么不能只判断周末” |
| 已会写策略,想看核心逻辑 | 直奔 §4 关键代码片段 + §6 两种用法 |
| 自己写的时候跑不出预期结果 | 跳到 §8 排错表 |
| 第一次看我的文章 | 先看 §9 免责声明,再决定怎么用 |
要解决的是一个非常具体、几乎每个实盘策略都会遇到的问题:
今天是周六、是国庆节、还是正常的交易日?我的策略今天到底该不该干活?
这个判断看起来简单,但写错的位置、错的时点、错的数据前提,都会让你的策略在休市日空跑报错,或者在交易日该干活的时候安静地待机——两种情况都不好排查。
本文不给”复制粘贴就能用”的一整份封装模块,而是把判断逻辑拆成几个独立片段讲清楚。原因很实在:交易日判断只有十几行核心逻辑,真正值钱的是”为什么这么排”;你自己组装一遍,以后接任何策略都不用再回来翻文章。
二、为什么”判断交易日”是实盘策略的第一行代码
先看三个真实场景:
场景 1:定时任务在周末被触发。
你用 run_time 注册了一个每天 09:25 的定时任务。QMT 的定时器在周末和节假日照样会触发——它不懂交易所日历。如果你的回调里直接读行情、查持仓,轻则拿到过期数据,重则直接报错。
场景 2:临时休市日没有日历兜底。
周末之外,还有春节、国庆等法定节假日,以及交易所的临时休市安排。只靠”今天几号”这种本地规则没法覆盖,必须有交易所日历做最终裁决。
场景 3:调休周末。
A股周末从不交易,但法定节假日和调休安排每年都在变。只判断周末,遇到国庆、春节就会误判;完全依赖日历接口,又会在每次回调里白白多调一次接口。
所以正确的姿势是分层递进:先用零成本的办法把周末秒排除,剩下的工作日再交给交易所日历裁决。这就是下一节的内容。
三、原理:三级递进判断
判断一个日期是否为 A 股交易日,我们分三级来做,每一级都是”能早退就早退”:
第 0 级:日期格式校验
传入的日期先归一化成 YYYYMMDD,格式不对直接抛异常。这一级是为了防御——防止 2026-10-5、20261005 (带空格)这类脏数据一路带到底,最后得到一个莫名其妙的结论。
第 1 级:周末快速过滤(零成本)
Python 的 datetime.weekday() 返回 0=周一 ... 5=周六, 6=周日。>= 5 就是周末。
关键认知:A 股周六周日从不交易(哪怕调休上班日也是休市的)。所以”周末 → 一定不是交易日”这条规则不存在例外,可以放心地直接返回 False,不需要查任何接口。
这一级的价值是性能:周末触发的定时任务、启动在周末的策略,在这里就结束了,一次日历接口都不用调。
第 2 级:交易日历校验(权威裁决)
调用 QMT 的 ContextInfo.get_trading_dates,取”目标日期往前 N 个交易日”:
- 如果目标日期是交易日 → 返回列表的最后一个元素恰好等于它;
- 如果目标日期不是交易日(法定节假日等)→ 列表最后一格早于它。
所以判断条件就是一行:dates[-1] == date_str。
两个使用前提,缺一不可:
- 只能在
after_init/handlebar/run_time回调中调用,不能放在init里——QMT 的机制是init执行完成前,交易日历类接口尚未就绪。 - 客户端必须先下载”节假日数据”(菜单:数据管理 → 节假日数据),否则日历不完整,法定节假日可能被误判。这个数据建议每年年初更新一次。
判断流程图
1 | 传入日期 |
四、实现思路与关键代码片段
先看整体形状。你最终写出来的东西,职责上应该就这四个部分——结构清楚比代码短更重要:
1 | 判断入口 is_trading_day(日期) |
下面按这四步给关键片段。
文件编码提醒:QMT 内置 Python 要求源文件为 GBK 编码,文件第一行必须有
# coding:gbk。下面的片段你要单独存成文件时,注意这一点。
片段 1:第 0 级 · 日期归一化
这一段的要点是”宽容输入、严格输出”——允许用户传 2026-10-05 这种写法,但内部统一成 YYYYMMDD:
1 | # coding:gbk |
片段 2:第 1 级 · 周末快速过滤
三行说完的事,但它是整个判断里性价比最高的一层:
1 | def _is_weekend(date_str): |
片段 3:第 2 级 · 交易日历查询与裁决
核心是接口调用 + 一个判断条件。注意 count 必须大于 0,period 用 '1d':
1 | def _get_calendar(C, end_date): |
参考标的为什么写死 '000001.SH'(上证指数)而不是用”当前图代码”?因为沪深两市共享同一套交易日历,而指数历史最完整、永不退市,行为最确定。你要改成 '399001.SZ' 结果也一样。
片段 4:缓存 —— 思路比代码重要
get_trading_dates 单次调用并不慢,但盘中回调一天可能触发上千次。加缓存的原则很简单:
1 | 查询前:目标日期已在缓存里? → 直接返回 |
对应到代码就几行,用一个模块级字典即可:
1 | _CALENDAR_CACHE = {} # { 'YYYYMMDD': [该日期前最近 N 个交易日, ...] } |
片段 5:把它们拼起来(判断入口的骨架)
1 | def is_trading_day(C, date_str=''): |
到这里,核心逻辑已经完整了。你会发现它总共不到 40 行——难的不是写不出来,而是知道为什么这么排:为什么周末必须先判、为什么日历空结果要打印、为什么不传日期时要小心”今天是哪台机器的今天”。这三条都在 §5 里展开。
五、几个容易做错的设计点
5.1 值得留出的几个可配置项
自己组装的时候,建议把这些做成模块顶部的常量,别散落在函数里——以后改一处就够:
| 可配置项 | 建议默认值 | 作用 | 什么时候改 |
|---|---|---|---|
| 参考标的 | 000001.SH |
查日历用的标的 | 基本不用改;沪深日历一致 |
| 周末快捷开关 | 开 | 周末零成本早退 | 需要严格逐日历裁决时可临时关闭 |
| 日历缓存天数 | 30 | 一次缓存多少个交易日 | 需要更长历史回看时调大 |
| 缓存容量上限 | 50 | 缓存多少个不同日期 | 长期运行的策略防内存增长 |
5.2 为什么周末判断要放在日历查询之前
反过来写也能跑,但代价是:每个周末触发的定时任务、每次周末启动的策略,都要白调一次日历接口。更重要的是语义清晰——周末不是交易日,这是不需要问任何人的确定性事实,本地能定的结论不要交给接口。
5.3 为什么日历为空时不能静默返回
is_trading_day 返回 False 有三种可能:周末、节假日、日历坏了。前两种是正常业务,第三种是环境故障。如果不打印提示,第三种会被静默吞掉——你以为今天是节假日,其实是数据没下载,这种误排查起来极其痛苦。所以正确做法是:保守返回 False + 显式打印原因。
5.4 不传日期时,”今天”是哪来的
缺省取的是 time.strftime('%Y%m%d')——QMT 所在机器的系统日期。两个注意点:
- 确保部署 QMT 的机器日期、时区正确(国内机器默认北京时间,一般无需处理)。
- 如果要判断的不是今天——比如收盘后判断”明天是不是交易日”,决定明早的定时任务要不要干活——显式传日期即可(
'YYYYMMDD'),其余判断流程完全相同。
5.5 关于”独立模块”
如果你希望它像工具库一样被复用,把上面这些片段存成一个独立文件(比如 trading_day.py)放在策略同目录,主策略里 import 即可——这也是我本地一直在用的组织方式,好处是以后每个新策略第一行就能用上它。这一步是工程习惯问题,留给你按自己的目录规范处理。
六、两种典型用法(实盘)
场景 1:策略启动时判断今天该不该干活
在 after_init 里调用,非交易日直接让策略待机:
1 | def after_init(C): |
场景 2:定时任务回调先过交易日关
run_time 注册的定时回调,在周末和节假日照样会被触发,所以回调的第一件事就是判断:
1 | def after_init(C): |
落地三步走
- 先下数据:QMT 界面 → 数据管理 → 节假日数据,确认已下载(每年年初记得更新)。这一步不做,后面全白搭。
- 先自测:单独写一个最小策略,在模拟模式下把
is_trading_day(C)的返回值打印出来,逐个验证:一个已知交易日、一个周六、一个法定节假日,三个都对再往下走。 - 再接入:把你的判断函数嵌进真实策略的
after_init和定时回调里。
建议给自己写一个自检函数:用同一个查询逻辑去查”今天”,如果返回空列表,说明日历不可用,打印一句明确提示。这一步能省掉你未来大量排查时间。
七、进阶:一个可扩展的方向
目前回答的是”是不是交易日”。如果你想进一步区分”今天是交易日,但现在是盘前 / 盘中 / 盘后”,可以在判断为交易日的前提下,用 time.localtime() 对当前时刻做区间判断(如 09:30–11:30、13:00–15:00 为盘中)。思路与本文一致:本地计算能解决的先本地解决,接口调用放在最后。这个留给你作为练习。
八、常见问题排查表
| 现象 | 最可能原因 | 解决办法 |
|---|---|---|
| 调用无结果 / 报接口错误 | 在 init 里调用了日历接口 |
移到 after_init / handlebar / run_time 回调中 |
| 法定节假日被判断为”交易日” | 节假日数据未下载或过期 | 数据管理 → 节假日数据,下载并每年年初更新 |
| 交易日被判断为”非交易日”且有警告打印 | 日历不可用(数据未下载) | 按打印提示下载节假日数据后重试 |
| 判断未来日期结果不对 | 本地日历未覆盖该未来日期 | 更新节假日数据;或推迟到临近再判断 |
自己写的模块 import 失败 |
模块与主策略不在同一目录 | 将两个文件放到同一策略目录 |
| 中文注释乱码 | 文件被保存为 UTF-8 | 用 GBK 编码重新保存,确认首行 # coding:gbk |
九、免责声明(请务必完整阅读)
阅读本文,即视为你已完整阅读、理解并同意以下全部条款。
1. 性质声明:本文及文中示例代码片段仅供编程学习与技术研究使用,不构成任何投资建议、荐股意见或收益承诺。 证券市场有风险,任何交易决策及其后果均由使用者独立作出并独立承担。
2. 模拟测试要求(重要): 在将本文思路或代码片段用于实盘环境之前,你必须在 QMT「模拟信号模式」或「模拟柜台」中进行充分测试,验证逻辑、数据前提、异常处理均符合预期。本文作者/发布者无法也不承诺对你的特定环境进行测试。未经充分模拟验证直接接入实盘所导致的一切后果,由使用者自行承担全部责任。
3. 数据准确性声明: 本文涉及的交易日历数据来源于 QMT 客户端本地数据。节假日数据未下载、未及时更新、交易所临时调整休市安排、客户端故障等,均可能导致判断结果错误。使用者应自行建立对判断结果的复核机制,不得将判断结果作为交易决策的唯一依据。
4. 无担保声明: 本文内容与示例代码按”现状”提供,不附带任何明示或默示的担保,包括但不限于对适销性、特定用途适用性、不侵犯第三方权利的担保。作者/发布者不保证示例代码无缺陷、不中断、不出错,也不保证读者自行实现的版本能达到预期效果。
5. 责任限制: 在任何情况下,作者/发布者均不对因使用或无法使用本文内容、示例代码而产生的任何直接、间接、附带、特殊、惩罚性损失(包括但不限于交易亏损、机会损失、数据丢失、停机损失)承担责任。即使作者/发布者已被告知此类损失的可能性。
6. 内容定性声明确认: 本文及其中的示例代码属于计算机编程技术内容,不构成证券投资咨询或投资顾问服务。作者不提供代客理财、代办交易、跟单操作、个股推荐或买卖时机建议等任何服务。若你因此类需求联系作者,请另行咨询具备中国证监会业务许可的持牌机构。
7. 版权声明: 本文版权归作者所有。本文免费分享,欢迎个人学习使用,也欢迎注明出处后自由转发;但未经书面授权,不得用于商业培训、付费社群、二次售卖,或以原创名义转载。发现侵权行为,作者保留追究法律责任的权利。
8. 合规使用: 使用者承诺遵守国家法律法规及所在券商的相关规定,不得将本文内容用于任何违法违规用途。
十、写在最后
交易日判断是策略体系里最不起眼、却每天都在被调用的一块地基。它写对的标志是你感觉不到它的存在;它写错的标志,是某个周末早上你收到一堆莫名其妙的报错。
核心逻辑不到 40 行,希望这篇文章给你的是”为什么这么排”,而不只是”复制哪一段”。把这段逻辑收进你自己的策略工具箱,以后每个新策略,第一行就是它。
⚠️ 最后再强调一次:任何代码在实盘使用前,请务必在模拟模式下充分测试。 市场有风险,交易需谨慎。