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) { 可以交易 }。这在单一市场、无夜盘、无节假日的场景下勉强可用,但一旦接入多市场就会迅速崩溃。交易日历的核心作用体现在四个层面:
- 订单合法性校验:非交易时段拒单,避免报单被交易所拒绝或产生废单。
- 行情与心跳管理:休市时停止订阅或降低心跳频率,节省资源并避免误判断线。
- 批处理调度:日终结算、对账、风控快照必须落在正确的交易日边界上。
- 策略逻辑正确性:跨市场套利需要知道两个市场同时开市的交集时段,否则会发出无法对冲的腿。
关键认知:交易日历不是“时间工具”,而是“业务规则引擎”。它把交易所的行政规则翻译成系统可查询的状态机。
三、常见交易所日历差异:一张表看清陷阱
不同交易所的日历差异远不止节假日不同。以下对比覆盖了亚太与欧美主要市场,重点展示容易导致系统故障的差异点。
| 交易所 | 交易日归属 | 典型时段特征 | 易错点 |
|---|---|---|---|
| 上交所 / 深交所 | 自然日,无夜盘 | 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存储;并提供回测与实盘一致的查询接口。
理解交易日历的定义、作用与交易所差异,是构建任何交易系统时间维度的第一步。它要求我们放弃“时间就是自然时间”的直觉,转而用规则驱动的视角看待每一个时间戳。只有当日历服务成为系统中唯一可信的时间语义来源,后续的时钟同步与Session管理才有坚实的根基。