源码级核验:src/platform/marketdata/models.py + src/platform/scheduling/
PanWatch 多市场时区与交易日历:为什么我的美股提醒时间不对、节假日还在推
PanWatch 同时覆盖 A 股、港股与美股,但三个市场的「一天」不是同一段时间,日历口径也不一样。本页把这三件事摊开:市场定义(时区与时段)、日历口径(谁能判节假日、谁只判周末)、以及最容易踩的UTC 分界(提醒的日触发上限在北京时间 08:00 重置,不是零点)。
PanWatch 三个市场的时区、时段与代码规则是什么?(源码定义)
下面的取值来自 src/platform/marketdata/models.py,不是文档转述;这三行决定了所有时间相关的行为。
| 市场 | 时区 | 交易时段(当地时间) | 代码格式正则 | 节假日怎么判 |
|---|---|---|---|---|
| A 股 CN | Asia/Shanghai | 09:30–11:30 与 13:00–15:00(含午间休市) | ^[036]\d{5}$ | 用 akshare 交易日历,包含法定节假日 |
| 港股 HK | Asia/Hong_Kong | 09:30–12:00 与 13:00–16:00 | ^\d{5}$ | 只判周末(无节假日日历) |
| 美股 US | America/New_York | 09:30–16:00(单时段,无午休) | ^[A-Z]{1,5}$ | 只判周末(无节假日日历) |
start <= t <= end,是两端闭区间——11:30 与 15:00 这两个整点仍算「在交易时段内」,因此整点前后的提醒不会被时段门槛挡掉。② 市场代码正则决定了你能填什么:A 股必须 6 位且以 0/3/6 开头(北交所等以 4/8 开头的代码不在这个正则内),港股 5 位数字,美股 1–5 位字母。PanWatch 的交易日历怎么判:A 股权威、港美股「诚实降级」
官方在 trading_calendar.py 的文档注释里明确写了数据源与降级策略,这段原话值得直接读。
src/platform/scheduling/trading_calendar.py(原文节选)数据源
- **A 股**:akshare 交易日历(`tool_trade_date_hist_sina`),含法定节假日,权威。
结果缓存在内存,由 `refresh()` 更新(启动预热 + 每日凌晨刷新)。
- **港股 / 美股**:没有等价的公开日历源,只判周末(诚实降级,不假装支持节假日)。
降级原则
拿不到日历时退回「只判周末」—— 宁可多发一条通知,也不能把交易日误判为休市。
少发一条是遗憾,漏发一整天是事故。| 情形 | 系统怎么判 | 对你意味着什么 |
|---|---|---|
| A 股,日历已加载且日期在覆盖区间内 | 按 akshare 日历判,法定节假日不开市 | 最准确的一档;启动时会预热,每日凌晨刷新 |
| A 股,日历拉取失败或为空 | 降级为只判周末 | 可能出现「休市日仍在跑」的空跑,日志里会有 warn |
| A 股,查询日期超出日历覆盖区间(如跨年未刷新) | 降级为只判周末 | 跨年时容易出现,建议确认日历刷新日志 |
| 港股 / 美股,任何日期 | 只判周末 | 港股与美股节假日会被判成交易日,可能空跑或产出无意义分析 |
| 周末(三个市场) | 一律不开市 | 零依赖、永远准确 |
PanWatch 最隐蔽的坑是什么?提醒的「每天」不是北京时间的一天
价格提醒的日触发上限与分钟去重桶,都用 UTC 时间生成。这直接影响「我设了每天最多推 3 次」到底按哪一天算。
src/modules/market/price_alert_engine.py(原文节选)def _day_key(now: datetime) -> str:
return now.astimezone(timezone.utc).strftime("%Y-%m-%d")
def _minute_bucket(now: datetime) -> str:
return now.astimezone(timezone.utc).strftime("%Y%m%d%H%M")| 你以为的行为 | 实际行为 | 会出现什么现象 |
|---|---|---|
| 每天最多推 3 次,按北京时间零点重置 | 按 UTC 零点重置,即北京时间 08:00 | 北京时间 00:00–08:00 之间触发次数仍计入前一天;08:00 后计数清零 |
| 同一分钟内重复命中会被去重 | 去重桶是 UTC 分钟(%Y%m%d%H%M) | 去重窗口等价于一个 UTC 分钟,与本地时钟的分钟对齐(分钟一致,无偏移) |
| 冷却时间按触发时刻算 | 冷却按真实时间差计算 | 冷却不受日界影响,行为符合直觉 |
cooldown_minutes)来控制频率——冷却不受日界影响,语义更稳定。PanWatch 夏令时:为什么美股的提醒时间一年会变两次
三个市场的时区都用 IANA 时区名交给 zoneinfo 处理,因此夏令时切换是自动的——但对你的感受不是。
| 时段 | 美股(美东) | 对应北京时间(夏令时) | 对应北京时间(冬令时) |
|---|---|---|---|
| 开盘 | 09:30 | 21:30 | 22:30 |
| 收盘 | 16:00 | 次日 04:00 | 次日 05:00 |
| 典型提醒窗口 | 盘中任意时点 | 北京时间深夜 | 北京时间深夜(再晚一小时) |
跨市场调度与夏令时的三步确认
settings.app_timezone,来自 TZ 环境变量,默认 Asia/Shanghai。如果服务器在别的时区却没设 TZ,cron 的语义会与你的直觉不一致。single 执行模式会逐个标的检查「该市场当下是否在交易时段」,不在就跳过;因此同一轮里 A 股标的可能被跳过、美股标的被执行。zoneinfo),不是项目里写死的表;这里给出的是量级参考,具体日期以当年夏令时切换公告为准。PanWatch 的调度与时段判定:batch 与 single 有什么区别?
同一个调度器上有两种执行模式,它们对「非交易时段」的处理完全不同,这是排查「为什么有的标的没跑」的关键。
| 执行模式 | 逐个标的检查交易时段? | 典型 Agent | 后果 |
|---|---|---|---|
batch | 不检查 | 盘前分析、收盘复盘 | 即使某市场休市也会整批执行,容易产出空分析 |
single | 检查,非时段即跳过并记日志 | 盘中监测、TradingAgents 深度分析 | 同一轮里只处理当下开市的市场;A 股与美股不会在同一时刻都被处理 |
src/modules/automation/agent_scheduler.py(single 模式节选)for stock in list(context.watchlist):
market_def = MARKETS.get(stock.market)
if market_def and not market_def.is_trading_time():
skipped += 1
logger.info(f"[调度] 跳过 {agent.display_name} {stock.symbol}"
f"({market_def.name} 非交易时段)")
continuesingle)实际会在不同时段分别处理不同的标的——这是设计如此,不是漏跑。要确认某个标的是否被跳过,看调度日志里的「非交易时段」行,或查 agent_runs 里单只模式的执行/跳过计数。PanWatch 时间类问题的六种表现与成因
下表把「时间不对」类问题拆成可判定的场景;每条的成因都能在源码里找到对应行。
| 现象 | 成因 | 怎么确认与规避 |
|---|---|---|
| 美股提醒在北京时间凌晨才来 | 美股 09:30 对应北京 21:30(夏令时),盘中即北京时间深夜 | 这是时区事实,不是错;用静默时段或单独渠道处理美股提醒 |
| 节假日还在推送 | 港股/美股只判周末,节假日被判成交易日 | 查 trading_calendar.py 的降级说明;节假日可临时停用该标的或规则 |
| A 股休市却跑了盘前分析 | 盘前分析是 batch 模式,不检查交易时段 | 改用界面上的启用开关控制;或把标的临时移出自选 |
| 「每天最多 3 次」感觉提前重置了 | 日计数按 UTC 零点(北京 08:00)重置 | 用冷却时间代替日上限,语义更稳定 |
| 夜间静默没生效,凌晨还在响 | 静默策略默认时区是 UTC,与 TZ 不是同一个配置 | 把策略时区显式设成 Asia/Shanghai,或把时间换算成 UTC 再填 |
| 服务器换时区后 cron 全乱了 | Agent 调度时区来自 TZ | 固定设置 TZ;容器部署时写进启动参数或 compose 文件 |
PanWatch 怎么知道哪天开市?
A 股用 akshare 的交易日历接口 tool_trade_date_hist_sina,含法定节假日,结果缓存在内存,启动时预热、每日凌晨刷新。港股与美股没有等价的公开日历源,只判周末——官方在代码注释里把这称为「诚实降级,不假装支持节假日」。
为什么港股或美股节假日还会推送?
因为港美股只判周末:节假日在该实现下会被判成交易日,Agent 会照常跑。官方的取舍理由是「宁可多发一条通知,也不能把交易日误判为休市」——在缺日历的前提下,漏发一整天的代价被认为更大。想避免,可用静默时段或临时停用规则。
A 股日历拉不到会怎样?
降级为只判周末,并在日志里 warn。也就是说日历故障时 A 股也会退化成「节假日照跑」。另外若查询日期超出日历覆盖区间(典型场景是跨年未刷新),同样降级。
提醒的「每天最多几次」按哪一天算?
按 UTC 日算,也就是北京时间每天早上 08:00 重置计数(不是零点)。如果这个语义不符合你的预期,建议改用冷却时间(cooldown_minutes)控制频率,它按真实时间差计算、不受日界影响。
我可以让不同市场用不同的提醒时间吗?
可以。提醒规则里可以设置「仅交易时段生效」(market_hours_mode),系统会按该标的所属市场的时段判定;同时 Agent 的 single 模式也会自动跳过非交易时段的市场。但要注意盘前/收盘这类 batch Agent 不做时段检查。
服务器时区会影响什么?
Agent 调度触发时间与界面时间展示都跟随应用时区(TZ,默认 Asia/Shanghai)。但通知静默时段策略的默认时区是 UTC,两者需要分别确认。建议在容器启动参数或 compose 文件里固定 TZ。
夏令时切换需要我手动改配置吗?
不需要。三个市场都用 IANA 时区名(如 America/New_York),换算交给时区库自动完成。你只需要注意「美股对应的北京时间会变」这个事实,并相应调整静默时段。