源码级核验:src/platform/marketdata/models.py + src/platform/scheduling/

PanWatch 多市场时区与交易日历:为什么我的美股提醒时间不对、节假日还在推

PanWatch 同时覆盖 A 股、港股与美股,但三个市场的「一天」不是同一段时间,日历口径也不一样。本页把这三件事摊开:市场定义(时区与时段)、日历口径(谁能判节假日、谁只判周末)、以及最容易踩的UTC 分界(提醒的日触发上限在北京时间 08:00 重置,不是零点)。

市场数:3(CN / HK / US)A 股日历:akshare 交易日历(含法定节假日)港股美股:只判周末(官方自述「诚实降级」)依据:marketdata/models.py + trading_calendar.py(固定 commit)
A 股 09:30–11:30 / 13:00–15:00
港股 09:30–12:00 / 13:00–16:00
美股 09:30–16:00
日历口径:A 股权威、港美股只判周末
依据 src/platform/marketdata/models.py 与 trading_calendar.py 整理的示意图;非官方架构图,时段与日历口径见本页表格。
Three markets

PanWatch 三个市场的时区、时段与代码规则是什么?(源码定义)

下面的取值来自 src/platform/marketdata/models.py,不是文档转述;这三行决定了所有时间相关的行为。

市场时区交易时段(当地时间)代码格式正则节假日怎么判
A 股 CNAsia/Shanghai09:30–11:30 与 13:00–15:00(含午间休市)^[036]\d{5}$用 akshare 交易日历,包含法定节假日
港股 HKAsia/Hong_Kong09:30–12:00 与 13:00–16:00^\d{5}$只判周末(无节假日日历)
美股 USAmerica/New_York09:30–16:00(单时段,无午休)^[A-Z]{1,5}$只判周末(无节假日日历)
两个细节值得记住。① 时段判定写作 start <= t <= end,是两端闭区间——11:30 与 15:00 这两个整点仍算「在交易时段内」,因此整点前后的提醒不会被时段门槛挡掉。② 市场代码正则决定了你能填什么:A 股必须 6 位且以 0/3/6 开头(北交所等以 4/8 开头的代码不在这个正则内),港股 5 位数字,美股 1–5 位字母。
Calendar

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 官方在这个取舍上的原话值得抄下来:「宁可多发一条通知,也不能把交易日误判为休市。少发一条是遗憾,漏发一整天是事故。」也就是说,它故意选择在不确定时按「开市」处理。理解这条,你就不会把「节假日还在推」当成 bug——它是设计取舍的必然结果,而你可以用静默时段或停用该市场的标的来规避。
UTC boundary

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 分钟,与本地时钟的分钟对齐(分钟一致,无偏移)
冷却时间按触发时刻算冷却按真实时间差计算冷却不受日界影响,行为符合直觉
为什么会这样:提醒引擎的时间基准统一走 UTC,只有「现在几点」这类展示走应用时区。实践建议是把日触发上限当成「跨零点的软上限」:如果你的策略是早盘容易连触发,把上限设得宽松一点,或直接用冷却时间(cooldown_minutes)来控制频率——冷却不受日界影响,语义更稳定。
Daylight saving

PanWatch 夏令时:为什么美股的提醒时间一年会变两次

三个市场的时区都用 IANA 时区名交给 zoneinfo 处理,因此夏令时切换是自动的——但对你的感受不是。

时段美股(美东)对应北京时间(夏令时)对应北京时间(冬令时)
开盘09:3021:3022:30
收盘16:00次日 04:00次日 05:00
典型提醒窗口盘中任意时点北京时间深夜北京时间深夜(再晚一小时)

跨市场调度与夏令时的三步确认

  • 先确认调度时区:Agent 调度器用 settings.app_timezone,来自 TZ 环境变量,默认 Asia/Shanghai。如果服务器在别的时区却没设 TZ,cron 的语义会与你的直觉不一致。
  • 再确认单只标的的时段判定:single 执行模式会逐个标的检查「该市场当下是否在交易时段」,不在就跳过;因此同一轮里 A 股标的可能被跳过、美股标的被执行。
  • 最后确认静默时段:通知静默时段策略的时区默认是 UTC,与调度时区是两套配置。想「北京时间 23:00–07:00 不打扰」,必须把它换算成 UTC 再填,或显式把策略时区设成应用时区。
  • 上图的美东—北京对应关系由时区库自动换算(zoneinfo),不是项目里写死的表;这里给出的是量级参考,具体日期以当年夏令时切换公告为准。
    Scheduling

    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} 非交易时段)")
            continue
    实用推论:如果你同时持有 A 股与美股标的,盘中监测(single)实际会在不同时段分别处理不同的标的——这是设计如此,不是漏跑。要确认某个标的是否被跳过,看调度日志里的「非交易时段」行,或查 agent_runs 里单只模式的执行/跳过计数。
    Time pitfalls

    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,因此不提供「实际观察到的时区行为」这类运行证据。若你部署后观察到的行为与本文不一致,请以你所用版本的源码与日志为准——项目发布节奏较快(0.10.2 到 0.16.1 共 10 个版本),时间相关逻辑可能已经调整。
    FAQ

    PanWatch 多市场时区与交易日历常见问题

    答案基于固定 commit 的源码与官方文档;版本与实现细节以官方仓库为准。

    相关页面:数据源矩阵与主备降级 · 自动化 Agent 与条件提醒

    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),换算交给时区库自动完成。你只需要注意「美股对应的北京时间会变」这个事实,并相应调整静默时段。

    时间搞清楚了,下一件事是什么数据进了分析?

    PanWatch 覆盖 11 类行情数据,每类都有主备源与缓存策略;进分析与进提醒的数据口径不完全一样。