2. 交易日历基础

在交易系统的世界里,时间并不是一条均匀流淌的直线。同样一个“2024-10-01 09:30:00”,对上海期货交易所、纽约商品交易所和伦敦金属交易所而言,可能分别意味着休市、正常交易和已经收盘。交易日历(Trading Calendar)正是用来回答“某一天、某一时刻,某个市场到底开不开门”的基础设施。它看似只是一张日期表,却直接影响订单路由、行情订阅、结算批处理、风控阈值切换乃至跨市场套利策略的成败。

一、核心定义:交易日历到底描述什么

交易日历是一组按交易所、品种或市场维度组织的规则集合,用于判定任意时间点是否属于可交易时段,并给出该时段的边界与属性。它通常包含以下要素:

  • 交易日(Trading Day):交易所认定的一个完整业务日,可能与自然日不一致,例如夜盘归属下一交易日。
  • 交易时段(Session):开盘、收盘、集合竞价、连续竞价、午休等具体时间窗口。
  • 节假日与临时休市:法定假日、交易所公告休市、极端天气或技术故障导致的临时停牌。
  • 半日市(Half Day):如港股在除夕、圣诞前夕仅上午交易。
  • 结算与交收日:与交易日关联但独立,影响资金可用与持仓过户。

从数据结构看,交易日历可以抽象为“日期 → 时段列表 → 状态”的映射。状态至少包括:OPEN、CLOSED、PRE_OPEN、AUCTION、HALT。系统在启动时加载日历,并在每次时间推进时查询当前状态。

二、作用:为什么不能只用系统时间判断

很多初学者会写这样的判断:if (now.getHours() >= 9 && now.getHours() < 15) { 可以交易 }。这在单一市场、无夜盘、无节假日的场景下勉强可用,但一旦接入多市场就会迅速崩溃。交易日历的核心作用体现在四个层面:

  1. 订单合法性校验:非交易时段拒单,避免报单被交易所拒绝或产生废单。
  2. 行情与心跳管理:休市时停止订阅或降低心跳频率,节省资源并避免误判断线。
  3. 批处理调度:日终结算、对账、风控快照必须落在正确的交易日边界上。
  4. 策略逻辑正确性:跨市场套利需要知道两个市场同时开市的交集时段,否则会发出无法对冲的腿。

关键认知:交易日历不是“时间工具”,而是“业务规则引擎”。它把交易所的行政规则翻译成系统可查询的状态机。

三、常见交易所日历差异:一张表看清陷阱

不同交易所的日历差异远不止节假日不同。以下对比覆盖了亚太与欧美主要市场,重点展示容易导致系统故障的差异点。

交易所 交易日归属 典型时段特征 易错点
上交所 / 深交所 自然日,无夜盘 09:30–11:30,13:00–15:00 午休时段行情快照仍可能推送,但不可交易
上期所 / 大商所 / 郑商所 夜盘归属下一交易日 21:00–次日02:30 不等,品种差异大 周五夜盘归属下周一,节假日前后夜盘停开规则复杂
港交所(HKEX) 自然日 09:30–12:00,13:00–16:00 半日市、台风休市、黑色暴雨延迟开市
纽交所(NYSE) 自然日 09:30–16:00(美东) 夏令时切换导致北京时间偏移一小时
伦敦金属交易所(LME) 自然日 环形交易、电子盘多时段 银行假日与英国特有假日,非全球统一

从表中可以看出,最危险的差异集中在三点:夜盘归属、半日市与临时休市、夏令时切换。这三类问题在回测中往往被忽略,却在实盘中造成真金白银的损失。

四、案例与场景:一次夜盘归属引发的对账事故

某量化团队在周五夜盘做螺纹钢跨期套利。系统按自然日将周五21:00的成交记入周五账目,但交易所实际将其归属为下周一交易日。结果周五日终结算时,系统发现持仓与交易所对不上,自动触发风控平仓,造成不必要的滑点损失。复盘后发现,问题不在策略,而在交易日历的“交易日归属”字段被简单实现为自然日。

// 错误实现:按自然日归属
String tradingDay = LocalDate.now().toString();

// 正确实现:查询交易日历,夜盘归属下一交易日
String tradingDay = calendar.resolveTradingDay(exchange, instrument, now);
// 对于周五21:00的螺纹钢,返回下周一日期

最佳实践:所有与“交易日”相关的字段,包括成交、持仓、结算、风控快照,都必须通过统一的日历服务解析,禁止在业务代码中直接使用自然日。

五、误区与复盘:交易日历的五个常见坑

  • 误区一:用节假日API代替交易所日历。 法定节假日不等于交易所休市日,交易所可能因纪念日、系统维护单独休市。
  • 误区二:忽略临时休市。 台风、极端天气、技术故障导致的临时休市必须支持热更新,不能只靠静态表。
  • 误区三:时区处理草率。 用本地时间存储日历,跨时区部署时必然错乱。应统一用UTC存储,展示时再转换。
  • 误区四:夜盘归属一刀切。 不同品种夜盘收盘时间不同,归属规则也可能不同,必须按品种粒度配置。
  • 误区五:回测与实盘共用一套简化日历。 回测若忽略半日市和临时休市,会高估策略容量和收益。

复盘结论:交易日历的复杂度不在于“查表”,而在于规则的多维性和动态性。一个健壮的日历服务应支持:按交易所、品种、日期查询状态;支持热更新临时休市;支持夜盘归属解析;支持时区无关的UTC存储;并提供回测与实盘一致的查询接口。

交易日历:核心结构与交易所差异 日历服务核心 交易日归属解析 夜盘 → 下一交易日 时段状态查询 OPEN / CLOSED / AUCTION 临时休市热更新 台风 / 故障 / 公告 UTC存储 + 时区转换 跨时区部署一致 回测/实盘统一接口 避免逻辑分叉 主要交易所日历差异对比 上交所 / 深交所 09:30–11:30 / 13:00–15:00 · 无夜盘 · 午休不可交易 上期所 / 大商所 / 郑商所 夜盘归属下一交易日 · 品种收盘时间不同 · 节前夜盘停开 港交所(HKEX) 半日市 · 台风休市 · 黑色暴雨延迟开市 纽交所(NYSE) 09:30–16:00 美东 · 夏令时切换影响北京时间 伦敦金属交易所(LME) 环形交易 + 电子盘多时段 · 英国特有银行假日

理解交易日历的定义、作用与交易所差异,是构建任何交易系统时间维度的第一步。它要求我们放弃“时间就是自然时间”的直觉,转而用规则驱动的视角看待每一个时间戳。只有当日历服务成为系统中唯一可信的时间语义来源,后续的时钟同步与Session管理才有坚实的根基。