跳转至

监控层与数据源扩展

更新:2026-10-03。本文记录两件事:实时能力与数据源的调研结论,以及监控层 在本仓库的落地方式。调研分两路:对参考产品(TradingVane)的实测拆解,以及对 GitHub 开源生态的选型调查(结论均核对过仓库元数据与源码,非二手转述)。


一、参考产品的实时能力拆解(tradingvane.com)

TradingVane 是一个面向交易员的全球市场情报仪表盘。拆解它的实时链路,是为了 回答一个问题:"异动喊人"这件事,成熟形态长什么样。

实时链路

环节 观察到的实现
传输 前端 SPA 建立长连接后推送(界面态:LIVE / ▲ 新消息 / 正在建立实时情报连接…),非轮询式刷新
门控 首屏先过一个混淆的 tv-guard 脚本建立"短期安全视觉会话",会话不可用时降级保留静态版本而不是白屏
渲染 /library/ 用滚动驱动的图像序列,按 devicePixelRatio × deviceMemory × connection.effectiveType 分 4k/hd 两档预加载

可借鉴的是降级策略:建立不了实时连接时展示最后可得的内容,而不是报错。 这与我们监控层的取向一致(见 §三「取数失败不中断循环」)。

数据源

类别 来源 备注
A 股资金 同花顺 · 东方财富(沪深主力净额) 页面直接标注来源
全球新闻 GDELT 关键词自动生成"最近 7 天"全球新闻追踪源
订阅源 RSS 2.0 / Atom + OPML 导入 用户可自建追踪源
头条 多信源实时聚合,含"突发 · 宏观"分类 —
日历 经济日历(驱动"下一重大事件"倒计时) —
其他 全球指数、汇率、大宗、央行、冲突与灾害 manifest 自述覆盖范围

一个刻意的反面样本:它内置的"披萨指数"页面自己写明 非实时 · 时段模型, 数据仅由华盛顿当地小时权重生成,不使用真实订单、客流或官方活动数据。 把不可靠的指标显式标注为不可靠——这个习惯值得我们对每个新数据源都坚持 (对应本仓库的「证明不了就别出数」纪律)。

对我们的结论

参考对象的价值在产品形态(订阅源可扩展、异动触发、降级兜底),不在前端工程 (AbortController 长连接、混淆守卫、逐 release canary 那套是为反克隆服务的,与 我们无关)。我们缺的是它有的常驻盯盘 + 推送,而不是它的视觉层。


二、GitHub 生态调研

结论 1:A 股没有免费的真流式行情,轮询是正确解

免费库(akshare / easyquotation / efinance / adata / mootdx)全部是 HTTP 轮询, 包括新浪 hq.sinajs.cn 和腾讯 qt.gtimg.cn 这两个"看起来像推送"的端点。 真 tick 流只有三条路:付费统一 API、TDX WebSocket 协议、券商终端(QMT/xtquant、Futu OpenD)。

因此监控层设计为轮询循环 + 可调间隔,而不是 socket 订阅者。已落地的 realtime_data.py 走腾讯主源 + 新浪兜底,这正是生态里(如 PanWatch、Ashare) 反复出现的组合。

结论 2:PanWatch 是本项目监控层最接近的架构范本

TNT-Likely/PanWatch(~1.9k★,MIT,Python 3.10+) 是自托管的 A 股/港股/美股监控器:规则引擎 + 调度扫描 + 多渠道通知 + 冷却/日限额/过期。

它的 price_alert_engine.py 里有三条经验,其中一条我们已经实现:

  1. 落盘先于通知(durability ordering) —— 原文要求"外部通道 I/O 只在命中和 收件箱事件持久化之后开始"。一次通知超时绝不能让告警丢失。 → 本仓库已实现:WatchEngine.tick() 的顺序是 store.append() → store.mark_sent() → notifier.send(),并有一条测试专门锁住这个顺序(test_callback_failure_does_not_lose_event)。
  2. 门控链 —— enabled → expire_at → 交易时段 → 每日上限 → 只触发一次 → 冷却。 → 我们已实现交易时段与冷却;每日上限与过期未做(见 §五)。
  3. TTL 缓存 —— 报价 5s、K 线 60s,避免在轮询循环里反复捶打数据源。 → 未做,当前每个 tick 都真实请求。

调度上它用 AsyncIOScheduler,作业配置为 jitter=20, coalesce=True, max_instances=1, replace_existing=True —— 这是扫描循环的正确参数组合(防重入、防堆积、防惊群)。

结论 3:不要为六个操作符引入重量级规则引擎

评估了 durable_rules(MIT,但 206 个未决 issue,Rete/状态机模型远重于 "价格 > X 且 涨幅 > Y")、business-rules(面向业务决策表,不面向流式行情状态)。 结论是自己写小 DSL —— 即本仓库 monitor/rules.py 的「规则 dict + 纯函数求值器」, 约 150 行、可脱离网络单测、许可证清晰。

数据源选型(按可用性排序)

用途 选择 ★ 许可证 Key 判断
实时快照 腾讯 qt.gtimg.cn + 新浪 hq.sinajs.cn — 端点公开 无 已采用;腾讯字段最全(含量比、涨跌停价)
批量快照 easyquotation ~3k MIT 无 标的很多时值得引入,一次请求取一批
深度/更快 mootdx(TDX 协议) ~1.5k MIT 无 pytdx 的维护替代品;注意 2024-07 后未更新,需钉版本
市场宽度/历史 akshare ~22.8k MIT 无 已是核心依赖;慢,适合 EOD/宏观,不适合进轮询循环
全球/美股/汇率 yfinance ~15k Apache-2.0 无 非官方,需包一层容错
加密 ccxt ~35k MIT 无 —
宏观 FRED — 官方 API 免费 key 接 vintage 时必须钉在 as-of 日(见 前视偏差防护 A10 缺口)
新闻 GDELT 2.0 Doc API — 公开 API 无 直接调 HTTP,见下方风险
RSS feedparser — BSD-2 无 接财联社/东财/同花顺电报

许可证风险登记表(重要)

项目 风险 处置
mpquant/Ashare、sngyai/Sequoia-X 无许可证 只读设计,不复制代码
rainx/pytdx 已归档(2020)+ 无许可证 不用,用 mootdx
freqtrade、sansan0/TrendRadar、linwoodc3/gdeltPyR GPL-3.0 本项目 MIT,仅作设计参考,不复制代码;GDELT 自己调 API
eltdx 许可证 NOASSERTION 用前先确认
investpy 上游 README 自称已坏 不用;investiny 是逆向 investing.com,脆弱且 ToS 有风险
durable_rules 非许可证问题,是过重 不引入

三、本仓库已落地

实时行情源 dataflows/realtime_data.py

  • 腾讯 qt.gtimg.cn 主源、新浪 hq.sinajs.cn 兜底,均免 Key;
  • 解析出涨跌幅、换手率、量比、振幅、PB、总市值、涨停/跌停价等字段;
  • 硬门控:调用方给了 curr_date 且不等于运行当天 → 抛 VendorError 拒绝出数。 实时快照无法证明分析日当时可知,喂进 --date 回测就是前视偏差;
  • 按 dataflows/errors.py 契约抛类型化错误(限流 → VendorRateLimitError, 全源无数据 → NoMarketDataError,传输异常 → VendorError);
  • 已注册进 VENDOR_METHODS["get_realtime_quote"],可走 route_to_vendor。

监控层 monitor/

与 graph/(LLM 流水线)刻意解耦:监控层不认识 LLM,可以长时间常驻, 成本只有行情请求。

模块 职责
events.py MonitorEvent 事件模型 + 严重度分级(info/notice/warning/critical)
rules.py 规则 DSL:change_pct / price / limit_move / near_limit / turnover / amount / volume_ratio / amplitude,支持按标的覆盖
engine.py 轮询循环、交易时段门控、去重、落盘、推送;on_event 钩子供上层触发深度分析
notify.py 通知分发:console / webhook / 企业微信 / 钉钉 / 飞书 / Server酱 / PushPlus / Bark
store.py JSONL 台账 + 冷却状态(原子写,重启不重复喊)

设计上沿用了调研结论:落盘先于推送;单条规则写错不拖垮整批求值;通道发送 失败只记 warning 不抛出;取数失败跳过本轮而不退出进程。

CLI

astock-trader watch 600519 000001 --once          # 跑一轮,核对规则
astock-trader watch 600519 --interval 15         # 常驻
astock-trader watch --test-notify                 # 验证通知通道

配置集中在 ~/.astock_trader/monitor.json(标的 + 规则 + 通知通道)。

覆盖测试

tests/test_monitor.py + tests/test_realtime_data.py:规则边界、冷却与重启去重、 通道分发与失败隔离、交易时段边界、引擎在取数失败/回调失败下的行为、 以及历史日期门控。


四、数据源广度:扩展清单

已落地:GDELT 全球新闻源 dataflows/gdelt_data.py

对接 GDELT 2.0 DOC API(免 Key),补上"全球事件面"的空白——地缘冲突、灾害、 政策、供应链这类会外溢到 A 股的事件,比个股新闻更早出现。已作为 get_global_news 的第三顺位(mx → eastmoney → gdelt)接入路由表。

时点门控:把查询区间上界钉死在分析日 23:59:59(enddatetime),历史运行 看不到分析日之后的报道。

两个必须知道的限制:

  1. GDELT 对出口 IP 限流"每 5 秒一次",429 响应里写得很直白。本模块做了 客户端节流(_MIN_INTERVAL_S),避免多个 Agent 并发把自己打成 429。
  2. 共享出口会被别人拖累。实测:同一个查询在直连时 200、走本地代理时 429 —— 代理出口 IP 已被其他用户打满。用得上的话给该域名单独走直连 (NO_PROXY=api.gdeltproject.org)。另外 DOC API 只索引最近约 3 个月, 更早的历史需要 GDELT 归档文件,此时模块抛 NoMarketDataError 让路由换源。

待办(按"免费 + 免 Key + 许可证干净"排序)

  1. feedparser + RSS(BSD-2)—— 财联社/东财电报流,成本极低、稳定性高。
  2. easyquotation(MIT)—— 监控标的变多后替换手写批量取数。
  3. 全球指数/汇率(yfinance,Apache-2.0)—— 给 A 股判断补宏观上下文。
  4. 北向资金 / 涨停板 / 板块资金流(akshare 已有对应函数,接进 VENDOR_METHODS 即可)。

每接一个新源,必须过 前视偏差防护 §「新增数据源时的检查清单」。


五、仍未做(诚实清单)

缺口 说明
触发的深度分析 WatchEngine.on_event 钩子已留好,但尚未接 analyze 流水线;即"异动 → 自动跑一次 15 角色分析"还没打通
每日触发上限 / 规则过期 PanWatch 门控链里的这两项未实现,长时间常驻时噪音可能偏大
交易日历 当前只按周一至周五 + 时段判断,法定节假日会照常空转轮询(多花请求,不会出错)
调度器 现在是朴素 sleep 循环,未换成 APScheduler(换来 jitter/coalesce/max_instances 的防重入保证)
报价 TTL 缓存 每 tick 都真实请求;标的多、间隔短时会放大对数据源的压力
通知通道 自研零依赖实现,未采用 Apprise(BSD-2,~17.5k★)。取舍见下

关于不引入 Apprise 的取舍:Apprise 一个依赖覆盖 90% 通道、维护成本低; 但它对 企业微信/ServerChan/PushPlus 的支持较薄(PanWatch 也仍需为这几个自写 httpx 适配器),且会引入新依赖。当前自研实现覆盖了本项目需要的全部通道且零依赖, 代价是通道种类要自己维护。若后续通道需求明显变多,再评估切换。