Sequoia-X 项目研究站 · 飞书播报
Sequoia-X 飞书播报:按策略分群推送的 webhook 路由
推送模块把选股结果组装成飞书交互卡片,再按策略的 webhook_key 决定发到哪个群:环境变量里配了 STRATEGY_WEBHOOK_<KEY> 就推给对应机器人,没配就回落到默认 Webhook。这类设计的实际价值是——海龟突破和定增公告可以进不同的群,不用在代码里改收件人。
Sequoia-X 的飞书播报卡片长什么样
源码构造的是飞书 interactive 卡片,字段固定。
| 卡片部分 | 源码内容 | 适用场景 | 注意点 |
|---|---|---|---|
| 消息类型 | msg_type = "interactive" | 需要结构化排版的播报 | 不是纯文本消息,网关或中转层需要支持卡片格式 |
| 标题 | 📈 Sequoia-X 选股播报 | {策略名},模板蓝色 | 一眼分辨是哪个策略的结果 | 策略名取的是类名(如 MaVolumeStrategy),不是中文别名 |
| 摘要行 | 日期 / 策略 / 选股数量三项 | 快速确认当天是否跑过 | 日期取的是执行当天(date.today()),不是数据源的最后一个交易日 |
| 分隔线 | hr 元素 | 视觉分层 | 纯展示,不影响数据 |
| 选股列表 | 每个标的一段 lark_md 链接:[名称](https://xueqiu.com/S/<代码>) | 点进去直接看个股页 | 名称要先查库外的股票基础信息,查不到就用代码兜底 |
| 链接前缀 | 6 开头 → SH;4 / 8 开头 → BJ;其余 → SZ | 生成雪球代码 | 与数据层入库时的 sh./sz. 规则不同,别混淆 |
| 空结果 | 列表为空时不会走到推送(main.py 先判断命中数) | 避免空卡片刷屏 | 推送器内部虽有「(无选股结果)」兜底文案,正常流程不会用到 |
Sequoia-X 七个策略的 webhook 标识是什么?
标识定义在各策略类里,环境变量名由配置模块扫描前缀得到。
| 策略类 | webhook_key | 环境变量名 | 适用场景 |
|---|---|---|---|
| MaVolumeStrategy | ma_volume | STRATEGY_WEBHOOK_MA_VOLUME | 均线放量信号单独建群 |
| TurtleTradeStrategy | turtle | STRATEGY_WEBHOOK_TURTLE | 突破类信号单独建群 |
| HighTightFlagStrategy | flag | STRATEGY_WEBHOOK_FLAG | 旗形整理信号单独建群 |
| LimitUpShakeoutStrategy | shakeout | STRATEGY_WEBHOOK_SHAKEOUT | 洗盘信号单独建群 |
| UptrendLimitDownStrategy | limit_down | STRATEGY_WEBHOOK_LIMIT_DOWN | 错杀信号单独建群 |
| RpsBreakoutStrategy | rps | STRATEGY_WEBHOOK_RPS | 强势股信号单独建群 |
| PrivatePlacementStrategy | private_placement | STRATEGY_WEBHOOK_PRIVATE_PLACEMENT | 公告事件单独建群 |
| 基类默认值 | default | 回落到 FEISHU_WEBHOOK_URL | 必填项,缺失则配置校验直接失败 |
STRATEGY_WEBHOOK_ 开头的键收集进一个映射(键名去掉前缀并转小写);推送时用策略的 webhook_key 去查这个映射,查不到就用默认 Webhook。所以键名的大小写与拼写必须与策略类里的标识一致。Sequoia-X 飞书配置:从建机器人到验证
按顺序做完这五步,推送链路才算打通。
在飞书群里添加自定义机器人
在目标群设置里添加「自定义机器人」,拿到该机器人的 Webhook 地址。开源项目不包含机器人创建能力,这一步在飞书侧完成。
先配一个默认 Webhook
把地址写进项目根目录
.env的FEISHU_WEBHOOK_URL。它是必填项——不填的话程序在读取配置阶段就会失败退出,连数据同步都不会开始。按策略补充专属 Webhook
想让某个策略单独进群,就在
.env里加对应的STRATEGY_WEBHOOK_<KEY>,例如STRATEGY_WEBHOOK_TURTLE。没配的键自动回落到默认地址。跑一次日常模式观察日志
执行
python main.py,每条命中的策略都会打印一行推送结果:成功是「飞书推送成功 [key],共 N 只股票」,失败会带上 HTTP 状态与飞书响应体。到群里核对卡片内容
确认标题里的策略名、日期、数量与标的一一对应。若同一只股票出现在多条卡片里,那是因为它同时满足多个策略——这是预期行为。
飞书推送成功 [turtle],共 8 只股票 / 飞书推送失败 [turtle] HTTP状态=400 飞书响应=...。真实的运行记录本站没有采集,页面只描述源码会打印什么。Sequoia-X 推送失败怎么判定与排查
最容易踩的坑:HTTP 200 不等于推送成功。
| 现象 | 源码判定 | 适用场景 | 处理方向 |
|---|---|---|---|
| HTTP 200 但没收到消息 | 响应体 code != 0 仍记为失败 | 地址失效、机器人被移除、内容被风控 | 看日志里的飞书响应体,按返回码定位 |
| 日志出现请求异常 | 捕获 requests.RequestException 后记 ERROR | DNS / 网络 / 代理问题 | 换网络环境或用能访问飞书的机器执行 |
| 单次推送耗时 10 秒 | 请求超时设为 10 秒 | 网络拥塞或服务端响应慢 | 超时只影响这一条推送,不会中断后续策略 |
| 进了错误的群 | 用 webhook_key 查映射,查不到回落默认 | 环境变量名拼错、大小写不一致 | 对照上表逐个核对键名 |
| 推送失败但数据已更新 | 推送异常被吞掉,不抛出、不影响主流程 | 数据入库与推送解耦 | 推送失败不会让当天数据白拉,修复后可重跑(当天重写是安全的) |
| 完全没有推送日志 | 命中为空时 main.py 直接跳过推送 | 阈值严或当天行情不匹配 | 先看策略是否命中,再看推送 |
Sequoia-X 卡片里的股票名称从哪里来
卡片上显示中文名而不是代码,这一步是推送前额外做的查询。
| 环节 | 实现 | 适用场景 | 注意点 |
|---|---|---|---|
| 查询方式 | 推送前逐只调用 baostock 基础信息接口取名称 | 命中数量不多时成本可接受 | 这是一次额外登录与逐只查询,命中多时推送阶段会变慢 |
| 失败兜底 | 查不到名称时用雪球代码(如 SZ000001)当显示名 | 数据源临时不可用 | 不会因为取名称失败而放弃整条推送 |
| 名称缓存 | 源码未做持久化缓存 | 每日一次推送场景够用 | 频繁手动重跑会重复查询,注意上游接口负担 |
| 链接格式 | lark_md 的 [名称](url) 语法 | 点击直达个股页 | 卡片需以 lark_md 渲染,纯文本模式看不到链接 |
| 与库内代码的关系 | 库内存纯数字代码,推送时再转带前缀格式 | 保持存储与展示解耦 | 两条转换规则不一致(sh./sz. 与 SH/SZ/BJ),改代码时别互相替换 |
| 去重 | 同一策略内部返回的清单已是去重结果 | 定增策略尤其需要(同一标的可能多次定增) | 跨策略之间不做合并去重 |
.env 等本地配置里,不要提交到版本库、不要贴进截图或公开文档。本站示例中出现的地址均为占位符。Sequoia-X 的推送怎么验证真的通了:三步判定链
「程序跑完了」和「消息发出去了」是两件事。飞书的成功判定在源码里写得很明确,照下面三步走一遍,五分钟能分清是哪一层坏了。
先用命令行直连 Webhook,把网络和地址排除掉
# 把 URL 换成你自己的飞书自定义机器人地址 curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","text":{"content":"SEQ-X 连通性测试"}}' "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx"返回体里
code为 0 且群里出现消息,说明地址与网络没问题,问题在程序侧;返回 4xx 或code非 0,问题在地址、机器人权限或被限流。再看程序日志里的两个关键字
飞书推送成功 [ma_volume],共 3 只股票 # 成功:HTTP 200 且飞书返回 code == 0 飞书推送失败 [ma_volume] HTTP状态=200 飞书响应={...} # 失败:HTTP 200 但 code 非 0 飞书推送请求异常 [ma_volume]:... # 请求层异常:超时、DNS、连接被拒Sequoia-X 源码的判定是
resp.status_code != 200 or resp_json.get("code") != 0即视为失败——HTTP 200 也可能是失败,所以只看「有没有报错」会漏掉这种情况。最后核对卡片字段是否与数据一致
-- 卡片里的日期是执行日(date.today()),与库内最新交易日未必相同 SELECT MAX(date) FROM stock_daily;卡片日期取自「执行当天」,而选股依据是「库内最新一根 K 线」。两者不一致时(非交易日、增量失败、时区差异),以库内日期为准,先补数再看结果。
| 推送失败会连带影响 | 实际情况 | 你需要做什么 |
|---|---|---|
| 程序退出码 | 不受影响。推送异常在 send() 内部被捕获并记 ERROR 日志,主流程继续往下走,最终正常结束 | 不要把「退出码 0」当成推送成功的证据 |
| 是否自动重试 | 源码没有重试逻辑,一次失败就是失败 | 确认地址后手动补发:把当天命中的代码贴回群里,或重跑一次日常模式 |
| 其它策略的推送 | 每个策略独立 send(),一个失败不影响后面的策略 | 看日志逐一确认,而不是「有一条失败就全部重跑」 |
| 库内数据 | 完全无关。推送只读结果不写库 | 不用因为推送失败重跑回填 |
本站实测:一次真实推送的请求体长什么样
本站在本机起了一个 mock Webhook 服务接收请求(不向任何真实飞书群发消息),再让项目按正常流程推送。收到的三条请求原文如下,可直接对着它排查自己的推送为什么没显示。
| 策略 | webhook_key | 实际请求 path | 卡片标题 |
|---|---|---|---|
| MaVolumeStrategy | ma_volume | /hook/ma_volume | 📈 Sequoia-X 选股播报 | MaVolumeStrategy |
| TurtleTradeStrategy | turtle | /hook/turtle | 📈 Sequoia-X 选股播报 | TurtleTradeStrategy |
| RpsBreakoutStrategy | rps | /hook/rps | 📈 Sequoia-X 选股播报 | RpsBreakoutStrategy |
命中的请求体(截取第 1 条,msg_type 为 interactive,即飞书卡片消息):
{
"msg_type": "interactive",
"card": {
"header": {"title": {"tag": "plain_text",
"content": "📈 Sequoia-X 选股播报 | MaVolumeStrategy"},
"template": "blue"},
"elements": [
{"tag": "div", "text": {"tag": "lark_md",
"content": "**日期:** 2026-10-10\n**策略:** MaVolumeStrategy\n**选股数量:** 5"}},
{"tag": "hr"},
{"tag": "div", "text": {"tag": "lark_md",
"content": "**选股列表:**\n[东望时代](https://xueqiu.com/S/SH600052) [同仁堂](https://xueqiu.com/S/SH600085) ..."}}
]
}
}
路由真的生效了(这是本轮最有价值的一条)
三条请求分别落到
/hook/ma_volume、/hook/turtle、/hook/rps——说明STRATEGY_WEBHOOK_<KEY>的名字(小写匹配webhook_key)确实能把你带到不同机器人,而不是全都 fallback 到默认 URL。要自查是否真的分群,最直接的办法就是给每个 key 配一个不同的群,然后看哪个群收到哪条。成功判定:看返回体的
code,不是看 HTTP 状态码resp_json = resp.json() if resp.status_code != 200 or resp_json.get("code") != 0: logger.error(f"飞书推送失败 [{webhook_key}] HTTP状态={resp.status_code} 飞书响应={resp.text}")本站 mock 服务返回的是
{"code": 0, "msg": "success"},日志随即打出飞书推送成功 [key]。反过来说:HTTP 200 但code非 0 依旧算失败,而日志里那行ERROR 飞书推送失败会同时带上飞书的原始响应,排查时直接看这一行就够。三个典型「推不出去」的原因(对照请求体能直接分辨)
现象 看请求体的哪里 结论 日志里根本没有「飞书推送」这一行 ——(没有请求体) 不是推送问题:策略 0 只时项目直接 跳过推送,先去看策略层有请求、飞书回 code != 0响应体里的 msg(如签名/关键词校验失败)机器人安全设置问题,与数据、策略无关 有请求、飞书回 200 且 code: 0,但群里没看到请求体里的 content是否为空、卡片elements是否被安全设置过滤通常是机器人被移出群或被限流;本项目不会重试,也不会告诉你没送达
飞书播报常见问题
不配飞书 Webhook,Sequoia-X 能用吗?
不能。默认 Webhook 是配置模型的必填字段,缺失时初始化配置就会抛校验错误,程序在启动阶段退出。如果你只想要数据不想要推送,需要自行改造代码,这超出本站整理范围。
Sequoia-X 一个群能收到所有策略结果吗?
可以。只配 FEISHU_WEBHOOK_URL 不配任何 STRATEGY_WEBHOOK_* 时,所有策略都回落到这个默认地址,结果都会进同一个群。想分组就补对应的环境变量。
Sequoia-X 推送成功但群里看不到?
先确认消息类型:源码发的是 interactive 卡片,如果接收端或中转层只支持纯文本,可能被丢弃。其次确认机器人仍在群里且有发送权限,再核对日志里是否真的打印了「飞书推送成功 [key]」。
Sequoia-X 推送失败要重跑整个流程吗?
通常不必。推送与数据入库是解耦的:推送异常只记日志,不影响已经写入的数据。修好 Webhook 后手工重跑当天是安全的,因为日常模式写入前会先删除当日记录。
Sequoia-X 卡片日期为什么是执行日?
源码里卡片日期取的是程序执行当天的日期,而不是数据源中最后一个交易日的日期。节假日补跑时两者可能不一致,判断数据新鲜度应结合策略命中情况与日志里的同步条数。
能改成钉钉、企业微信或邮件吗?
仓库里的通知模块只实现了飞书,没有其他渠道的代码。要接别的渠道需要自己按 notify/ 目录的结构新增模块并在 main.py 中调用。以上以仓库当前源码为准。
推送失败会自动重试吗?会让程序报错退出吗?
都不会。源码里 send() 把请求异常与飞书返回的失败都记成 ERROR 日志,不抛出、不重试,主流程继续执行并正常结束。所以「程序退出码 0」并不代表消息发出去了——判断推送是否成功请以日志里的「飞书推送成功 [key]」为准,以官方源码为准。