Sequoia-X 项目研究站 · 飞书播报

Sequoia-X 飞书播报:按策略分群推送的 webhook 路由

推送模块把选股结果组装成飞书交互卡片,再按策略的 webhook_key 决定发到哪个群:环境变量里配了 STRATEGY_WEBHOOK_<KEY> 就推给对应机器人,没配就回落到默认 Webhook。这类设计的实际价值是——海龟突破和定增公告可以进不同的群,不用在代码里改收件人。

消息形态:交互卡片(interactive)路由:7 个策略 key + 默认回落依据 notify/feishu.py 与 core/config.py(2026-09 核验)
7 个策略类各自带一个 webhook_key
环境变量路由STRATEGY_WEBHOOK_<KEY>
未配置回落用默认 FEISHU_WEBHOOK_URL
成功判定响应体 code == 0
飞书推送路由示意(依据 notify/feishu.py 与 core/config.py 整理,非官方流程图)。
依据来源官方 README · pyproject · .env.example
本页引用sequoia_x/notify/feishu.py · core/config.py · .env.example
核验方式源码静态核验 + 本站实测(2026-10-10 在 Windows / Python 3.11.9 真机跑通安装、回填、日常运行与推送)
核验基准commit 444c0db(2026-05-09)· Python ≥3.10 · 2026-10-12 质检复核(含 2026-10-10 本站实测)
Card

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 先判断命中数)避免空卡片刷屏推送器内部虽有「(无选股结果)」兜底文案,正常流程不会用到
Routing

Sequoia-X 七个策略的 webhook 标识是什么?

标识定义在各策略类里,环境变量名由配置模块扫描前缀得到。

策略类webhook_key环境变量名适用场景
MaVolumeStrategyma_volumeSTRATEGY_WEBHOOK_MA_VOLUME均线放量信号单独建群
TurtleTradeStrategyturtleSTRATEGY_WEBHOOK_TURTLE突破类信号单独建群
HighTightFlagStrategyflagSTRATEGY_WEBHOOK_FLAG旗形整理信号单独建群
LimitUpShakeoutStrategyshakeoutSTRATEGY_WEBHOOK_SHAKEOUT洗盘信号单独建群
UptrendLimitDownStrategylimit_downSTRATEGY_WEBHOOK_LIMIT_DOWN错杀信号单独建群
RpsBreakoutStrategyrpsSTRATEGY_WEBHOOK_RPS强势股信号单独建群
PrivatePlacementStrategyprivate_placementSTRATEGY_WEBHOOK_PRIVATE_PLACEMENT公告事件单独建群
基类默认值default回落到 FEISHU_WEBHOOK_URL必填项,缺失则配置校验直接失败
路由是怎么生效的:配置模块在初始化后遍历环境变量,把所有以 STRATEGY_WEBHOOK_ 开头的键收集进一个映射(键名去掉前缀并转小写);推送时用策略的 webhook_key 去查这个映射,查不到就用默认 Webhook。所以键名的大小写与拼写必须与策略类里的标识一致。
Setup

Sequoia-X 飞书配置:从建机器人到验证

按顺序做完这五步,推送链路才算打通。

  1. 在飞书群里添加自定义机器人

    在目标群设置里添加「自定义机器人」,拿到该机器人的 Webhook 地址。开源项目不包含机器人创建能力,这一步在飞书侧完成。

  2. 先配一个默认 Webhook

    把地址写进项目根目录 .env 的 FEISHU_WEBHOOK_URL。它是必填项——不填的话程序在读取配置阶段就会失败退出,连数据同步都不会开始。

  3. 按策略补充专属 Webhook

    想让某个策略单独进群,就在 .env 里加对应的 STRATEGY_WEBHOOK_<KEY>,例如 STRATEGY_WEBHOOK_TURTLE。没配的键自动回落到默认地址。

  4. 跑一次日常模式观察日志

    执行 python main.py,每条命中的策略都会打印一行推送结果:成功是「飞书推送成功 [key],共 N 只股票」,失败会带上 HTTP 状态与飞书响应体。

  5. 到群里核对卡片内容

    确认标题里的策略名、日期、数量与标的一一对应。若同一只股票出现在多条卡片里,那是因为它同时满足多个策略——这是预期行为。

日志格式示例(行为来自源码,非真实运行记录):
飞书推送成功 [turtle],共 8 只股票 / 飞书推送失败 [turtle] HTTP状态=400 飞书响应=...。真实的运行记录本站没有采集,页面只描述源码会打印什么。
Failure

Sequoia-X 推送失败怎么判定与排查

最容易踩的坑:HTTP 200 不等于推送成功。

现象源码判定适用场景处理方向
HTTP 200 但没收到消息响应体 code != 0 仍记为失败地址失效、机器人被移除、内容被风控看日志里的飞书响应体,按返回码定位
日志出现请求异常捕获 requests.RequestException 后记 ERRORDNS / 网络 / 代理问题换网络环境或用能访问飞书的机器执行
单次推送耗时 10 秒请求超时设为 10 秒网络拥塞或服务端响应慢超时只影响这一条推送,不会中断后续策略
进了错误的群用 webhook_key 查映射,查不到回落默认环境变量名拼错、大小写不一致对照上表逐个核对键名
推送失败但数据已更新推送异常被吞掉,不抛出、不影响主流程数据入库与推送解耦推送失败不会让当天数据白拉,修复后可重跑(当天重写是安全的)
完全没有推送日志命中为空时 main.py 直接跳过推送阈值严或当天行情不匹配先看策略是否命中,再看推送
Names

Sequoia-X 卡片里的股票名称从哪里来

卡片上显示中文名而不是代码,这一步是推送前额外做的查询。

环节实现适用场景注意点
查询方式推送前逐只调用 baostock 基础信息接口取名称命中数量不多时成本可接受这是一次额外登录与逐只查询,命中多时推送阶段会变慢
失败兜底查不到名称时用雪球代码(如 SZ000001)当显示名数据源临时不可用不会因为取名称失败而放弃整条推送
名称缓存源码未做持久化缓存每日一次推送场景够用频繁手动重跑会重复查询,注意上游接口负担
链接格式lark_md 的 [名称](url) 语法点击直达个股页卡片需以 lark_md 渲染,纯文本模式看不到链接
与库内代码的关系库内存纯数字代码,推送时再转带前缀格式保持存储与展示解耦两条转换规则不一致(sh./sz. 与 SH/SZ/BJ),改代码时别互相替换
去重同一策略内部返回的清单已是去重结果定增策略尤其需要(同一标的可能多次定增)跨策略之间不做合并去重
安全提示:Webhook 地址等同于向该群发消息的凭据,请写在 .env 等本地配置里,不要提交到版本库、不要贴进截图或公开文档。本站示例中出现的地址均为占位符。
Verify push

Sequoia-X 的推送怎么验证真的通了:三步判定链

「程序跑完了」和「消息发出去了」是两件事。飞书的成功判定在源码里写得很明确,照下面三步走一遍,五分钟能分清是哪一层坏了。

  1. 先用命令行直连 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,问题在地址、机器人权限或被限流。

  2. 再看程序日志里的两个关键字

    飞书推送成功 [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 也可能是失败,所以只看「有没有报错」会漏掉这种情况。

  3. 最后核对卡片字段是否与数据一致

    -- 卡片里的日期是执行日(date.today()),与库内最新交易日未必相同
    SELECT MAX(date) FROM stock_daily;

    卡片日期取自「执行当天」,而选股依据是「库内最新一根 K 线」。两者不一致时(非交易日、增量失败、时区差异),以库内日期为准,先补数再看结果。

推送失败会连带影响实际情况你需要做什么
程序退出码不受影响。推送异常在 send() 内部被捕获并记 ERROR 日志,主流程继续往下走,最终正常结束不要把「退出码 0」当成推送成功的证据
是否自动重试源码没有重试逻辑,一次失败就是失败确认地址后手动补发:把当天命中的代码贴回群里,或重跑一次日常模式
其它策略的推送每个策略独立 send(),一个失败不影响后面的策略看日志逐一确认,而不是「有一条失败就全部重跑」
库内数据完全无关。推送只读结果不写库不用因为推送失败重跑回填
Sequoia-X 与 EasyClaw 无已证实集成:飞书推送是 Sequoia-X 自带的 webhook 逻辑,本机技能路线也不提供把 Sequoia-X 结果转发到飞书的技能。以官方源码为准。
Measured: push

本站实测:一次真实推送的请求体长什么样

本站在本机起了一个 mock Webhook 服务接收请求(不向任何真实飞书群发消息),再让项目按正常流程推送。收到的三条请求原文如下,可直接对着它排查自己的推送为什么没显示。

策略webhook_key实际请求 path卡片标题
MaVolumeStrategyma_volume/hook/ma_volume📈 Sequoia-X 选股播报 | MaVolumeStrategy
TurtleTradeStrategyturtle/hook/turtle📈 Sequoia-X 选股播报 | TurtleTradeStrategy
RpsBreakoutStrategyrps/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) ..."}}
    ]
  }
}
  1. 路由真的生效了(这是本轮最有价值的一条)

    三条请求分别落到 /hook/ma_volume、/hook/turtle、/hook/rps——说明 STRATEGY_WEBHOOK_<KEY> 的名字(小写匹配 webhook_key)确实能把你带到不同机器人,而不是全都 fallback 到默认 URL。要自查是否真的分群,最直接的办法就是给每个 key 配一个不同的群,然后看哪个群收到哪条。

  2. 成功判定:看返回体的 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 飞书推送失败 会同时带上飞书的原始响应,排查时直接看这一行就够。

  3. 三个典型「推不出去」的原因(对照请求体能直接分辨)

    现象看请求体的哪里结论
    日志里根本没有「飞书推送」这一行——(没有请求体)不是推送问题:策略 0 只时项目直接 跳过推送,先去看策略层
    有请求、飞书回 code != 0响应体里的 msg(如签名/关键词校验失败)机器人安全设置问题,与数据、策略无关
    有请求、飞书回 200 且 code: 0,但群里没看到请求体里的 content 是否为空、卡片 elements 是否被安全设置过滤通常是机器人被移出群或被限流;本项目不会重试,也不会告诉你没送达
边界:以上是「项目发出的请求长什么样 + 本地 mock 的应答如何被判定」的真机读数;不等于已经验证过真实飞书群的送达与展示效果。真实环境的送达需要在你的群里用真实 Webhook 跑一次,以飞书官方文档与机器人的实际返回为准。
FAQ

飞书播报常见问题

不配飞书 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]」为准,以官方源码为准。

推送不通时怎么按排查表定位?

报错页把配置、数据、交易日与推送四类失败分开处理,并给出安全重跑方式。