源码级核验:agent_catalog.py + price_alert_engine.py + notifications/
PanWatch 什么时候会提醒你:Agent 剧本、六道门槛与推送去重
在 PanWatch 里,「我设了提醒但没收到消息」是最常见的问题,而答案往往不是「规则没命中」。本页按执行顺序拆开三件事:谁在什么时候跑(三个 Agent 的默认剧本)、命中了还要过哪几道门(触发门槛的判定顺序)、推送本身会不会被吞(去重与静默时段)。
PanWatch 三个 workflow Agent 的默认剧本:谁在什么时候跑、默认开不开
PanWatch 在 agent_catalog.py 里把 Agent 分成两类:workflow(用户可见、可调度)与 capability(内部/手动、不独立调度)。下面这张表是完整名单与默认值。
| Agent | 类型 | 默认 cron | 默认开关 | 执行模式 | 它做什么 |
|---|---|---|---|---|---|
盘前分析 premarket_outlook | workflow | 0 9 * * 1-5 | 关闭 | batch | 综合昨日分析与隔夜信息,展望今日走势 |
盘中监测 intraday_monitor | workflow | */5 9-15 * * 1-5 | 关闭 | single | 交易时段实时监控,判断是否有值得关注的信号 |
收盘复盘 daily_report | workflow | 30 15 * * 1-5 | 开启 | batch | 生成复盘报告:市场回顾、个股复盘与次日关注 |
图像技术分析 chart_analyst | capability | 无(不调度) | 关闭且不可见 | single | 已弃用:详情页按需触发的图像分析,lifecycle_status=deprecated,被另外三个替代 |
TradingAgents 深度分析 tradingagents | workflow | 无(手动触发) | 关闭 | single | 多 Agent 投资决策链,单次 3–5 分钟、约 0.05 美元 |
chart_analyst 虽然还在代码里,但被显式标为 deprecated(弃用)并写明由另外三个 Agent 替代,因此你不需要在界面上找它。盘中监测的默认参数(agent_catalog.py)config={
"event_only": True, # 只对有事件的情况做分析
"price_alert_threshold": 3.0, # 涨跌幅阈值(%)
"volume_alert_ratio": 2.0, # 量比阈值
"stop_loss_warning": -5.0, # 止损提醒(%)
"take_profit_warning": 10.0, # 止盈提醒(%)
"throttle_minutes": 30, # 同一标的的抑制窗口(分钟)
}event_only=True 加上 30 分钟的抑制窗口,是成本控制设计——每 5 分钟叫一次模型,一整天下来账单会很难看。这也解释了「为什么盘中不一定每次都推」:没达到阈值就没有事件,没有事件就不产出。PanWatch 的价格提醒能配什么:五类条件、八种运算符、两种组合逻辑
PanWatch 提醒规则的条件结构是「条件组 + 条件项」。条件项类型与运算符支持范围来自源码,不是界面截图猜测。
| 条件类型 | 取值来源 | 可用的运算符 | 注意点 |
|---|---|---|---|
price 价格 | 报价的 current_price | > >= < <= = == != <> 与 between(闭区间) | 最常见;整数写法即可 |
change_pct 涨跌幅 | 报价的 change_pct | 同上 | 注意与市场涨跌停规则配合,避免在涨停板上反复触发 |
turnover 成交额 | 报价的 turnover | 同上 | 单位与量级以数据源为准,建议先用一个宽松值试跑 |
volume 成交量 | 报价的 volume | 同上 | 停牌或半日市时数值会明显偏小 |
volume_ratio 量比 | 优先取报价字段(腾讯源提供),缺失时回退拉 K 线摘要 | 同上 | 美股等源可能没有量比字段,会多一次 K 线请求 |
| 组合方式 | 字段取值 | 判定规则 | 适用场景 |
|---|---|---|---|
| AND(默认) | op: "and" | 全部条件都命中才算命中 | 「跌破均线 且 放量」这类共振信号 |
| OR | op: "or" | 任一条件命中即算命中 | 「涨 5% 或 跌 5%」这类双向关注 |
| 非法或空条件组 | 条件项为空 / 类型不支持 | 判定为不命中(empty_items / unsupported_type) | 排查时先确认条件项真的存在且类型拼写正确 |
between(也接受 in)要求取值是一个二元数组,判定为闭区间——[10, 20] 表示 10 与 20 都算命中。另外条件项不支持嵌套:只能是「一组扁平条件 + and/or」,想做「(A 且 B) 或 C」需要拆成两条规则。命中了为什么 PanWatch 还不推:六道门槛与它们的判定顺序
这条顺序是排查 PanWatch 漏推的关键。它写在 _can_trigger() 里,按顺序短路返回——所以「哪个原因被拦」取决于它排在第几道。
| 顺序 | 门槛 | 不通过时的原因码 | 怎么调整 |
|---|---|---|---|
| ① | 规则是否启用 | disabled | 界面上的启用开关;repeat_mode=once 命中后会被自动关闭 |
| ② | 是否已过到期时间 | expired | 规则可设到期时间;留空表示永不过期 |
| ③ | 是否在允许的时段内 | non_trading | 仅当规则设为「只在交易时段生效」时拦截;手动触发可绕过 |
| ④ | 当日触发次数是否超上限 | daily_limit | 注意“当日”按 UTC 分界(北京 08:00 重置) |
| ⑤ | 冷却时间是否已过 | cooldown | cooldown_minutes;按真实时间差计算,不受日界影响 |
| ⑥ | 重复模式是否为「只触发一次」且已触发过 | once_triggered | 改成重复触发,或新建一条规则 |
src/modules/market/price_alert_engine.py(_can_trigger 节选)if not rule.enabled: return False, "disabled"
if rule.expire_at and now > exp: return False, "expired"
if rule.market_hours_mode == "trading_only"
and not _is_trading_time(market): return False, "non_trading"
if max_per_day > 0 and count >= max_per_day: return False, "daily_limit"
if rule.repeat_mode == "once" and rule.last_trigger_at:
return False, "once_triggered"
if delta_sec < cooldown: return False, "cooldown"
return True, "ok"max_instances=1——上一轮没跑完时本轮直接跳过。所以在「取数慢 + 规则多」的情况下,实际扫描间隔可能超过 60 秒。PanWatch 推送这一环怎么工作?命中记录、分钟去重、内容去重与静默时段
在 PanWatch 里,命中不等于推送成功。系统把这两件事分开记录,这也是「没收到消息」排查的抓手。
| 环节 | 行为 | 可观察的结果 |
|---|---|---|
| 命中落库 | 写入 PriceAlertHit,含触发快照与通知结果字段 | 即使推送失败,命中记录仍在 |
| 分钟去重 | 以 UTC 分钟桶作为去重键;同分钟重复写入会回滚并记为 duplicated | 同一分钟内不会重复产生命中记录 |
| 渠道解析 | 规则指定了渠道就用指定渠道(且仅限已启用);未指定则用系统默认渠道 | 改渠道后要确认该渠道仍是启用状态 |
| 推送内容 | 标题为「【价格提醒】名称 (代码)」,正文含规则名、现价、涨跌幅,以及最多 4 条命中条件明细 | 条件多的时候正文不会无限长 |
| 通知去重(批量 Agent) | 按 SHA1(Agent 名 + 标题 + 正文前 1200 字符) 做 TTL 窗口去重 | 重启或手动补跑不会重复轰炸 |
| 静默时段 | quiet_hours="HH:MM-HH:MM",支持跨午夜;start == end 视为全天静默;策略默认时区是 UTC | 夜间静默需要按 UTC 填,或显式改成应用时区 |
notify_success / notify_error),但规则计数照常增加。因此「没收到消息」有两种完全不同的成因:规则没命中(去看门槛顺序),或命中了但渠道发失败(去看命中记录里的失败原因)。另一个细节:去重逻辑出错时系统倾向「发送」而不是丢弃——这是为了避免因为去重模块故障导致漏报。PanWatch 没收到提醒怎么办?排查决策表
按这个顺序走,能在几分钟内定位到 PanWatch 的问题是出在规则、门槛还是渠道。
| 先确认 | 怎么看 | 结论与下一步 |
|---|---|---|
| 规则本身是否启用、是否已过期 | 提醒列表页的开关与到期时间 | 若被 once 自动关闭,需要重新启用或改重复模式 |
| 是否被时段门槛挡住 | 规则的「仅交易时段生效」设置 + 该标的所属市场当前是否开市 | 港股/美股节假日会被判为交易日;A 股休市日会被正确拦截 |
| 是否达到日上限或仍在冷却 | 命中历史里的触发时间分布 | 日上限按 UTC 日算(北京 08:00 重置);冷却按真实时间差 |
| 条件是否真的命中 | 用宽松阈值(如涨跌幅 0.1%)做一次验证 | 若宽松值也不动,问题在数据而不是规则:去查数据源健康度 |
| 渠道是否收到 | 渠道列表里该渠道是否启用;是否配了系统默认渠道 | 规则指定了渠道时,只会走指定渠道 |
| 是否被静默时段吞掉 | 通知策略里的 quiet_hours 与策略时区 | 默认时区是 UTC,填错时区会让静默窗口整体偏移 |
| 扫描是否在正常进行 | 日志里是否有每轮扫描记录;max_instances=1 时上一轮未结束会跳过本轮 | 取数慢会导致扫描变稀;可减少规则数或降低数据源压力 |
bypass_market_hours),因此「手动触发有、自动触发没有」几乎总是指向时段门槛或扫描节奏,而不是数据或渠道问题。PanWatch 自动化 Agent 与提醒常见问题
答案基于固定 commit 的源码与官方文档;具体默认值可能随版本调整,以你所用版本的配置为准。
相关页面:TradingAgents 研究链 · 多市场时区与交易日历
三个 Agent 默认都开着吗?
不是。三个 workflow Agent 里只有收盘复盘默认开启(cron 30 15 * * 1-5);盘前分析与盘中监测默认关闭,需要你在界面上手动启用。这就是「装完一天什么都没发生」最常见的原因。
盘中监测为什么每 5 分钟跑一次却不一定推消息?
默认配置是 event_only=True:只有出现达到阈值的事件(涨跌幅 3%、量比 2 等)才做分析;同一标的还有 30 分钟的抑制窗口(throttle_minutes)。这套设计的目的是控制模型调用成本,不是「每次都给你一段话」。
提醒支持哪些条件?能不能做「价格且放量」这种组合?
支持五类条件:价格、涨跌幅、成交额、成交量、量比;运算符含大于/小于/等于/不等与闭区间。组合方式有 AND(默认)与 OR 两种,可以做「跌破某价 且 量比 > 2」这类共振条件。但条件项不支持嵌套,「(A 且 B) 或 C」需要拆成两条规则。
为什么我设了「每天最多 3 次」,感觉不到中午就重置了?
因为日计数的「一天」是按 UTC 算的,重置点对应北京时间 08:00。如果你更在意频率而不是次数,建议用冷却时间(cooldown_minutes)——它按真实时间差计算,语义更稳。
支持哪些通知渠道?
官方 README 列出 Telegram、企业微信、钉钉、飞书、Bark 与自定义 Webhook,底层由 apprise 承担。渠道可以给单条规则指定,也可以设系统默认渠道。国内平台通常不需要代理,Telegram 一般需要自行配代理。
通知能不能去重?会不会重复轰炸?
有两层去重:① 价格提醒按 UTC 分钟桶去重,同一分钟内不重复产生命中;② 批量 Agent 的通知按「Agent 名 + 标题 + 正文前 1200 字符」的哈希做 TTL 窗口去重,所以重启或手动补跑不会重复发同样的内容。此外还有静默时段可用。
手动触发和自动触发的行为一样吗?
不完全一样。手动触发可以绕过交易时段门槛(用于验证规则本身是否正确),而自动触发会按完整的六道门槛判定。因此「手动有、自动没有」通常指向时段门槛或扫描节奏问题。