用 Hermes Agent + Python 搭一个本地财经简报系统:6 小时踩 13 个坑的全记录

42 阅读12分钟

用 Hermes Agent + Python 搭一个本地财经简报系统:6 小时踩 13 个坑的全记录

TL;DR:自己用 Hermes Agent + Python + RSS + yfinance + Telegram 搭一套完全本地化的每日财经简报系统。每天 8 点准时收到一份 1000 字的 Telegram 推送:4 大资产板块(黄金/美股/原油/加密)实时数据 + LLM 自动归因 + 风险预警。零商业 API 费用,零数据外泄,全栈可控。

这是我的第一篇博客,记录一次"从 0 搭一个真实有用的系统"的全过程。我没按理想路径写,而是按踩坑顺序写 —— 因为按教程跑通的人都能写,但真实工作中最有用的是踩坑记录


📖 目录

[TOC]


🎯 一、为什么自己搭

市面上财经 newsletter 服务一抓一大把,订阅就行。但有几个绕不过去的痛点:

痛点商业服务自己搭
数据源不可控,倾向某种叙事✅ 自己挑 RSS 源
推送时机服务方决定✅ cron 任意定
板块结构写死的✅ markdown + skill 自由改
关键词偏好无法配置✅ 词典 + 权重
数据驻留第三方拿走你的关注偏好✅ 全本地
成本一年几百到几千✅ ~0(用免费 LLM)

自己搭的代价是 6 小时学习成本,但搭完之后任何一处都能改


🏗️ 二、最终架构概览

┌─────────────────────────────────────────────────────┐
│  Hermes Agent (本地 LLM CLI, 内置 cron + skill + MCP)│
│                                                     │
│  Cron 任务:每天 8:00 触发                           │
│        ↓                                            │
│  Skill: daily-economic-news (定义推送格式)          │
│        ↓                                            │
│  LLM 调用 shell:                                     │
│  $ python fetch.py --report   ← 生成 markdown 报告   │
│  $ cat reports/YYYY-MM-DD.md  ← LLM 真实读取报告     │
│        ↓                                            │
│  LLM 按"数据+新闻+预警"格式生成综述                  │
│        ↓                                            │
│  send_message → Telegram                            │
└─────────────────────────────────────────────────────┘
              ↑ fetch.py 内部
              │
   ┌──────────┴──────────────────────────────────┐
   │  11 个 RSS 源(央行+国际+中文)              │
   │  + 4 个资产板块(gold/stocks/oil/crypto.py)│
   │  + SQLite 去重 + 关键词打分                  │
   │  + 多档时间窗口(央行 168h / 中文 96h / ... )│
   └─────────────────────────────────────────────┘

核心思想

  1. Python 脚本干重活(采集 / 打分 / 去重 / 计算技术指标)
  2. LLM 当"分析师" (读报告 + 跨板块归因 + 写预警)
  3. Hermes 当调度器(cron 触发 + skill 注入 + 推送渠道)

🚀 三、踩坑实录

坑 1:blogwatcher-cli 的 SSRF 防护(30 分钟)

Hermes 自带 80+ skill,其中 blogwatcher 看起来正合需求:"Monitor blogs and RSS/Atom feeds via blogwatcher-cli tool"。

curl -sL https://github.com/JulienTant/blogwatcher-cli/releases/latest/download/blogwatcher-cli_linux_amd64.tar.gz \
  | sudo tar xz -C /usr/local/bin blogwatcher-cli

blogwatcher-cli add "BBC Business" https://feeds.bbci.co.uk/news/business/rss.xml
blogwatcher-cli scan

报错

proxyconnect tcp: dial tcp 127.0.0.1:7890:
... is not authorized by the client: "127.0.0.1" address is loopback

诊断 1 小时,发现是 Go 标准库 net/http 的"防 SSRF"行为:Go 不允许 HTTPS 请求通过 loopback 或 RFC1918 地址(10.x、172.16-31.x、192.168.x)的代理。我换成 WSL 里 Windows 主机的真实局域网 IP 测试:

http_proxy=http://<WSL_IP>:7890 blogwatcher-cli scan
# 报错变成:
# ... is not authorized by the client: "192.168.X.X" address is private

报错措辞从 loopback 换成 private —— 这个限制是死的。所有家用网络的代理都在 RFC1918 段里,blogwatcher-cli 在国内 WSL 里根本无法工作。

但同样的代理 curl 完全 OK:

curl -v -x http://127.0.0.1:7890 https://www.google.com
# HTTP/1.1 200 Connection established

⚠️ 教训:选库别看名字漂亮就用,先验证它对你的网络环境是否可用。Python 的 feedparser 没有这个限制,30 行代码就能替代:

import feedparser
feed = feedparser.parse("https://feeds.bbci.co.uk/news/business/rss.xml")
print("status:", feed.get("status"))
print("entries:", len(feed.entries))
print("first:", feed.entries[0].title)
# status: 200, entries: 47, first: Spirit Airlines shutting down...

完美。换路线,自己写。


坑 2:WSL 代理配置(20 分钟)

WSL2 在 Windows 11 较新版本支持 mirrored 网络模式:

ip addr show
# inet 10.255.255.254/32 scope global lo   ← mirrored 模式特征
# eth1 inet 192.168.X.X/24                 ← 与 Windows 主机同网段

mirrored 模式下,WSL 里的 127.0.0.1 真的指向 Windows 的 127.0.0.1,所以 http://127.0.0.1:7890 理论上可用。

💡 关键前提:Clash 客户端必须开启 "Allow LAN"

  • Clash for Windows / Clash Verge:General → Allow LAN 打开
  • 不开的话,WSL 来源被识别为非"严格本地",连接会被拒绝

确认通了:

curl --connect-timeout 5 -I -x http://127.0.0.1:7890 https://www.google.com
# HTTP/1.1 200 Connection established

坑 3:reported 标记的"消费式" bug(30 分钟)

数据采集层第一版 fetch.py 用了"消费式"标记:

def get_unreported(conn, min_score):
    return conn.execute(
        "SELECT * FROM articles WHERE reported = 0 AND score >= ?", ...
    )

def mark_reported(conn, ids):
    conn.executemany(
        "UPDATE articles SET reported = 1 WHERE id = ?", ...
    )

# 使用:python fetch.py --report --mark-reported

跨日去重的初衷是:今天报告过的,明天不要再出现

但有 bug:同一天内多次触发时,第二次跑就拿不到第一次的内容了。

我调试时手动跑过几次 --mark-reported,把 26 条全 mark 完。然后真正的 8:00 cron 跑时,get_unreported 只返回 1 条(早 03:16 到 08:00 之间唯一一条新增的新闻),LLM 据此生成的简报"今日只有 1 条新闻",明显不对。

修复:把"已报告"判断从 boolean 改成时间窗口:

def get_unreported(conn, min_score, hours_window=24):
    """获取过去 hours_window 小时内、score 达标的所有文章。
    同一天多次触发时不会丢内容。"""
    return conn.execute("""
        SELECT * FROM articles
        WHERE score >= ?
          AND datetime(fetched_at) >= datetime('now', ?)
        ORDER BY tier, score DESC, published DESC
    """, (min_score, f"-{hours_window} hours"))

副作用:上次报告里出现过的新闻这次还可能出现。但只要在 24h 窗口内,被 score 排序自然排到底,不影响 LLM 抓 top N。

💡 教训:消费式标记是常见反模式。任何"用户多次触发同一动作"的场景都该用幂等设计(时间窗口状态机)。


坑 4:新浪 Au99.99 symbol 失效(40 分钟)

最初想用上海黄金交易所 Au99.99(人民币计价,国内主流)。新浪财经免费接口:

curl --noproxy '*' "http://hq.sinajs.cn/list=gds_AU99_99,hf_GC,DINIW"

输出:

var hq_str_gds_AU99_99="";                ← 空了!symbol 失效
var hq_str_hf_GC="4629.775,...";          ← 国际金有数据
var hq_str_DINIW="...,98.2092,..."        ← 美元指数有

gds_AU99_99 这个 symbol 不知什么时候被新浪下线了,没有公开的迁移说明。不死磕沪金,用国际金 hf_GC(COMEX 黄金)替代,加上美元指数 DINIW,再用 yfinance 拉历史数据算 MA5/MA20。

新浪接口的注意点(全是坑):

def fetch_sina_no_proxy(symbols):
    with httpx.Client(
        timeout=10,
        proxy=None,           # ⚠️ 关键 1:显式禁用代理
        trust_env=False,      # ⚠️ 关键 2:忽略环境变量代理
        headers={
            "Referer": "https://finance.sina.com.cn",  # ⚠️ 关键 3
            "User-Agent": "Mozilla/5.0",
        }
    ) as c:
        r = c.get(f"http://hq.sinajs.cn/list={symbols}")
    r.encoding = "gbk"        # ⚠️ 关键 4:GBK 不是 UTF-8
    return r.text

trust_env=False 是核心。httpx 默认会读 https_proxy / http_proxy 环境变量,但新浪是国内站,走代理会失败(被代理服务器丢去境外节点然后被新浪屏蔽)。

🔑 核心原则:访问国内站不走代理,访问国外站走代理。代码里要显式区分。


坑 5:yfinance 偶发 delisted(20 分钟)

yfinance 是 Python 拉雅虎财经数据的库,免费、覆盖广(美股 / 期货 / 加密 / 汇率全有)。但偶尔会报 delisted

$GC=F: possibly delisted; no price data found  (period=10d)

GC=F(COMEX 黄金主力合约)明明每天都在交易,但 yfinance 第一次调用偶尔会返回这个错误。第二次调用又能拿到数据。

修复:加重试 + 双源兜底:

def fetch_one(symbol, retry=2):
    for attempt in range(retry + 1):
        try:
            import yfinance as yf
            t = yf.Ticker(symbol)
            hist = t.history(period="30d")
            if hist.empty or len(hist) < 2:
                log.warning(f"{symbol} 数据不足,重试 {attempt+1}")
                time.sleep(1)
                continue
            return parse_hist(hist)
        except Exception as e:
            log.warning(f"{symbol}{attempt+1} 次失败: {e}")
            time.sleep(1)
    return {}

加密板块更稳,主源用 CoinGecko 免费 API(不要 key、限速 30 次/分),yfinance 兜底。


坑 6:LLM hallucination —— 不读 markdown 编内容(40 分钟)

最经典的一坑。Hermes Skill v1 这样写:

## 执行流程
1. 执行:python3 ~/news-fetcher/fetch.py --report
2. 读取:cat ~/news-fetcher/reports/$(date +%Y-%m-%d).md
3. 向用户简要汇报

跑完一次,Telegram 收到推送:

> ⚠️ 今日新闻量偏少(共 1 条 MarketWatch 文章)

📰 今日核心三件事:
1. 越南 4 月通胀超预期升温...
2. 美国通胀卷土重来...
3. 社安金资金争议升温 ← 报告里根本没有这条!LLM 在编!

LLM 没去 cat 那个 markdown 文件,直接从 fetch.py 的 stdout 日志里看到 "新增 0 条到 DB",然后自己脑补了三件事,第三件完全是 hallucination。

修复 SKILL.md,加上明确的"两步走"约束

### 步骤 1:生成报告
执行:cd /home/USERNAME/news-fetcher && /home/USERNAME/.venv/bin/python fetch.py --report

⚠️ fetch.py 的 stdout 是日志(包含 "新增 N 条" 这种 sqlite 去重统计,
**不代表当日新闻数量**),不要把日志当报告内容。

### 步骤 2:读取真实报告内容(必须)
执行:cat /home/USERNAME/news-fetcher/reports/$(date +%Y-%m-%d).md

**所有后续分析必须基于此命令的输出内容**。

### 严格禁止
- ❌ 跳过步骤 2(必须真实 cat 报告)
- ❌ 在「今日核心三件事」中编造不在报告里的条目

再触发一次,LLM 老老实实读了 markdown 文件,三件事全部出自报告,无幻觉。

💡 教训:LLM 给定指令的边界要明确。指令"读取报告"还不够,必须明确"必须 cat 这个文件,不要从其他来源推测"。LLM 在面对二义性时倾向于"猜更省事"。


坑 7:cron 不读 .bashrc

hermes cron run a72cc351f796
# 报错:ModuleNotFoundError: No module named 'feedparser'

cron 任务起的子进程不会读 ~/.bashrc,所以:

  • venv 没激活
  • 代理环境变量没注入

这个坑两个层面都要解决:

层面 1:venv —— 用绝对路径调 Python:

# ❌ 不要这么写(cron 下找不到依赖)
python3 fetch.py --report

# ✅ 用绝对路径
/home/USERNAME/.venv/bin/python fetch.py --report

层面 2:代理 —— 在 SKILL.md 里显式 export 或者用 wrapper 脚本:

# wrapper.sh
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
/home/USERNAME/.venv/bin/python /home/USERNAME/news-fetcher/fetch.py --report

⚠️ 重要:本文中所有 ~/... 写法都是为了脱敏,实际部署务必用 /home/YOUR_USERNAME/... 完整绝对路径。~ 在某些 cron 环境下不会展开。


坑 8:中文 RSS 源大批阵亡(30 分钟)

国际源都是英文,加几个中文源。批量 curl 测试 16 个候选:

状态条数
财新❌ XML 解析失败0
华尔街见闻❌ 空响应0
第一财经❌ 非标准 RSS0
新浪财经❌ HTTP 4040
21 世纪经济❌ HTTP 4040
36 氪30
经济观察网26
雪球热门20
FT 中文20
BBC 中文✅ 综合38(信号密度低)
DW 德国之声✅ 综合51(信号密度低)
RFI 华语❌ HTTP 3010

国内财经媒体大量停掉了 RSS(财新、华尔街见闻、新浪、21 世纪、第一财经……),剩下能用的不多。最终纳入 4 个:36 氪、经济观察网、雪球热门、FT 中文

📌 现状提醒:上面列表是 2026-05 的实测,你用的时候可能某些源又复活、某些挂掉。先 curl 测一遍再加进配置。


坑 9:FT 中文 0 条入选 —— 时间窗口杀手(15 分钟)

加进 sources.yaml 后跑 fetch.py:

2026-05-03 抓取: FT中文网
  20-> 过滤后 0 条     ← 全部被过滤掉?

诊断:

# 打印每条的时间和分数
[3] 德国、西班牙 4 月通胀率低于预期
   pub=2026-04-29 16:00 (4 天前)
   in_window=False (24h 窗口卡住了)
   score=4 (其实够分)

FT 中文 RSS 更新慢,最新一条都是 4 天前的。24h 窗口下全部被时间过滤。但内容仍有价值("通胀数据"这类宏观信号一周内都有意义)。

修复:给中文源单独配更长时间窗口:

- name: FT中文网
  url: https://www.ftchinese.com/rss/news
  hours_back: 96       # 4 天窗口
- name: 36
  hours_back: 72       # 3 天窗口
- name: 经济观察网
  hours_back: 72
- name: 雪球今日话题
  hours_back: 72

# 央行类用更长(一周一次声明)
- name: Federal Reserve
  hours_back: 168      # 7 天窗口
  bypass_keywords: true # 央行任何 release 都直接入选

💡 教训:同一个时间窗口阈值未必适合所有源。配置驱动 + 单源覆盖比"统一阈值 + 写死代码"灵活得多。


🛠️ 四、最终代码结构

4.1 项目目录

~/news-fetcher/
├── fetch.py              # 主脚本:抓 RSS + 整合资产板块 + 生成 Markdown
├── gold.py               # 黄金板块(COMEX hf_GC + DXY + yfinance MA)
├── stocks.py             # 美股板块(S&P / Nasdaq / Dow / VIX)
├── oil.py                # 原油板块(WTI / Brent,价差信号)
├── crypto.py             # 加密板块(CoinGecko 主 + yfinance 兜底)
├── sources.yaml          # RSS 源配置 + 关键词词典 + 时间窗口
├── data/news.db          # SQLite 去重数据库
├── reports/              # 每日生成的 Markdown 简报
└── logs/                 # 各模块日志

~/.hermes/
└── skills/research/daily-economic-news/SKILL.md  # LLM 指令

4.2 sources.yaml 核心配置

sources:
  - name: BBC Business
    url: https://feeds.bbci.co.uk/news/business/rss.xml
    weight: 0
    tier: market
    bypass_keywords: false

  - name: Federal Reserve
    url: https://www.federalreserve.gov/feeds/press_all.xml
    weight: 5
    tier: macro
    bypass_keywords: true     # 央行任何 release 都直接入选
    hours_back: 168           # 7 天窗口

  - name: FT中文网
    url: https://www.ftchinese.com/rss/news
    weight: 1
    tier: market
    hours_back: 96

# ... 其他源(共 11 个)

keywords:
  high:    # 命中加 3 分
    - 央行
    - 加息
    - 降息
    - Fed
    - FOMC
    - inflation
    - tariff
    - sanction
  medium:  # 命中加 2 分
    - 股市
    - 汇率
    - 原油
    - OPEC
    - 黄金
  company: # 命中加 1 分
    - IPO
    - 破产
    - merger
    - earnings
    - bailout

filter:
  hours_back: 24       # 默认窗口
  min_score: 3         # 入选门槛
  max_per_source: 8    # 每源最多入选条数

4.3 fetch.py 核心打分逻辑

def score_article(title, summary, kw_cfg):
    """关键词命中打分"""
    text = (title + " " + summary).lower()
    score = 0
    for k in kw_cfg["high"]:
        if k.lower() in text: score += 3
    for k in kw_cfg["medium"]:
        if k.lower() in text: score += 2
    for k in kw_cfg["company"]:
        if k.lower() in text: score += 1
    return score


def fetch_source(src, cutoff, kw_cfg):
    """抓取单个 RSS 源"""
    # 单源覆盖时间窗(央行等用更长回溯)
    if src.get("hours_back"):
        cutoff = datetime.now(tz.utc) - timedelta(hours=src["hours_back"])

    feed = feedparser.parse(src["url"])
    items = []
    for entry in feed.entries:
        pub = parse_published(entry)
        if pub and pub < cutoff:
            continue

        # 央行类 bypass 关键词
        if src.get("bypass_keywords"):
            score = src["weight"]
        else:
            score = score_article(entry.title, entry.summary, kw_cfg) \
                  + src.get("weight", 0)

        items.append({
            "id": article_id(entry.title, entry.link),
            "title": entry.title,
            "link": entry.link,
            "published": pub.isoformat() if pub else "",
            "source": src["name"],
            "tier": src.get("tier", "market"),
            "score": score,
        })

    items.sort(key=lambda x: -x["score"])
    return items[:max_per_source]

4.4 资产板块注入

最干净的做法:fetch.py 在生成报告时调用四个独立脚本,不是 import。这样每个脚本可独立跑、独立调试:

PYBIN = "/home/USERNAME/.venv/bin/python"
asset_modules = [
    ("gold.py",   "💰 黄金市场"),
    ("stocks.py", "📊 美股指数"),
    ("oil.py",    "🛢️ 原油市场"),
    ("crypto.py", "₿ 加密货币"),
]
for script, title in asset_modules:
    try:
        md = subprocess.check_output(
            [PYBIN, str(ROOT / script), "--markdown"],
            timeout=30, text=True,
            stderr=subprocess.DEVNULL,
        )
        lines.append(md)
    except Exception as e:
        log.warning(f"获取 {script} 数据失败: {e}")
        lines.append(f"\n## {title}\n\n_数据获取失败_\n")

4.5 Hermes Skill 关键片段

完整的 SKILL.md 约 130 行,核心是强制 LLM 按格式输出

## 执行流程(严格按顺序)

### 步骤 1:生成报告
执行:cd ~/news-fetcher && /home/USERNAME/.venv/bin/python fetch.py --report

### 步骤 2:读取真实报告(必须)
执行:cat ~/news-fetcher/reports/$(date +%Y-%m-%d).md

**所有后续分析必须基于此命令的输出**。

### 步骤 3:生成 Telegram 推送(严格按下方模板)

按以下结构生成。**4 个资产板块每个都必须独立成段,
结构为「数据 + 相关新闻总结 + 预警」三件套**:

```
📊 今日财经简报(YYYY-MM-DD)

📈 数据要点:宏观 N1 / 市场 N2 / 公司 N3,共 N 条

━━━━━━━━━━━━━━━━━━━━
💰 黄金板块
━━━━━━━━━━━━━━━━━━━━
COMEX $X.XX/oz  涨跌箭头 涨跌幅%  | 振幅 X.XX%
DXY 美元指数 X.XXXX  涨跌箭头 涨跌幅%
MA5 $X | MA20 $X(标注现价相对均线位置)

📰 相关新闻:
[2-3 句中文归因,基于报告里的新闻]

⚠️ 预警:
[关键阻力/支撑 + 触发条件,1-2 句]

━━━━━━━━━━━━━━━━━━━━
📊 美股板块 / 🛢️ 原油板块 / ₿ 加密板块(同样三件套)
━━━━━━━━━━━━━━━━━━━━

📄 完整报告:~/news-fetcher/reports/YYYY-MM-DD.md
⚠️ 本简报全文均为信号提示,非投资建议。
```

## 严格禁止
- ❌ 用裸 python3 或 /usr/bin/python3.12(cron 找不到依赖)
- ❌ 跳过步骤 2(必须真实 cat 报告,不能从 stdout 推测)
- ❌ 在「相关新闻」段落里编造不在报告中的新闻
- ❌ 4 个资产板块任意一个跳过

📨 五、最终成品效果

每天 8:00 准时收到 Telegram 推送:

📊 今日财经简报(2026-05-03)
📈 数据要点:宏观 15 / 市场 19 / 公司 8,共 42 条

━━━━━━━━━━━━━━━━━━━━
💰 黄金板块
━━━━━━━━━━━━━━━━━━━━
COMEX $4629.77/oz  🔺 +0.08%  |  振幅 2.23%
DXY 美元指数 98.2092  🔺 +0.11%
MA5 $4614.26 | MA20 $4718.21(↑高于MA5,↓低于MA20)

📰 相关新闻:
伊朗霍尔木兹海峡封锁持续收紧,叠加中美贸易摩擦反复,
令黄金避险需求获得支撑;美元指数微幅走强对金价形成
轻微压制,多空因素交织下金价以小幅震荡为主。

⚠️ 预警:
金价现报 $4629.77,短线高于 MA5 偏强,但受制于
MA20 中期压力;若美元指数突破 99,需警惕金价回调。
若 FOMC 释放鹰派信号则有望突破 MA20。

━━━━━━━━━━━━━━━━━━━━
📊 美股板块
━━━━━━━━━━━━━━━━━━━━
S&P 500   7230.12  🔺 +0.29%
Nasdaq    25114.44  🔺 +0.89%
Dow       49499.27  🔻 -0.31%
VIX       16.99  🔺 +0.59%

📰 相关新闻:
纳指领涨与道指下跌形成明显分化,科技股财报季表现
超预期叠加 AI 概念持续高烧推动资金向成长股迁移……

⚠️ 预警:
VIX 16.99 处正常区间但需保持警惕;S&P 已高于 MA20,
留意道指若持续走弱可能暗示板块轮动已至末期;
5 月财报季进入尾声,盈利预期修正需密切关注。

━━━━━━━━━━━━━━━━━━━━
🛢️ 原油板块(数据+新闻+预警)
━━━━━━━━━━━━━━━━━━━━
[省略]

━━━━━━━━━━━━━━━━━━━━
₿ 加密板块(数据+新闻+预警)
━━━━━━━━━━━━━━━━━━━━
[省略]

📄 完整报告:~/news-fetcher/reports/2026-05-03.md
⚠️ 本简报全文均为信号提示,非投资建议。

每板块「数据 + 新闻归因 + 预警」三件套,4 板块独立成段。预警段写出了真正的交易者视角 —— 具体阻力支撑位 + 触发条件。LLM 在做"分析师"的活,不是复读新闻。

更难得的是跨板块归因

加密板块:…当前加密与美股(尤其纳指)联动明显,若纳指出现回调需警惕加密跟跌风险…

LLM 主动把 4 个独立板块的数据联系起来分析。这是当初定 SKILL.md "驱动因子参考" 段落时埋下的伏笔 —— 每个板块下面写 5 条"该资产的核心驱动因子",LLM 看到后就有了跨板块联动的依据。


📊 六、踩坑清单(全 13 个)

序号解决方案耗时
1blogwatcher-cli 的 SSRF 防护换 Python feedparser30 min
2WSL 代理 + Allow LANClash 开 Allow LAN20 min
3新浪 Au99.99 symbol 失效换 hf_GC40 min
4新浪国内站不能走代理trust_env=False20 min
5httpx 默认读环境变量代理同上-
6yfinance 偶发 delisted重试 + 双源兜底20 min
7fetch.py reported 标记的消费式 bug改时间窗口30 min
8LLM 不读 markdown hallucinateSKILL.md 强制约束40 min
9cron 不读 .bashrc用绝对路径15 min
10FT 中文 RSS 更新慢单源时间窗口 96h15 min
11关键词词典中英文不对称加 30+ 中文财经词20 min
12推送格式信息密度低4 板块独立成段重构30 min
13weixin 推送 asyncio bughermes 内部问题,已知放弃-

💎 七、架构优势

7.1 配置驱动

想做的事改哪
新增 RSS 源sources.yaml 加一条
调关键词权重sources.yaml keywords
调时间窗口sources.yaml 单源 hours_back
改推送格式SKILL.md 几句话
改触发时间hermes cron edit 一条命令
换推送渠道--deliver telegram/discord/...
加新资产板块写个 xxx.py + fetch.py 加一行

0 行代码改动就能做大部分调整。

7.2 数据 / 逻辑 / 调度三层分离

配置层:sources.yaml
数据层:fetch.py + 4 个资产模块
逻辑层:SKILL.md (LLM 行为规范)
调度层:hermes cron
推送层:hermes deliver target

每一层独立可改、独立可测。

7.3 零商业 API 成本

  • RSS 源:全部公开免费
  • 黄金 / 美元指数:新浪财经免费接口
  • 美股 / 原油 / 加密:yfinance + CoinGecko(免费)
  • LLM:Hermes 配置的免费试用渠道

7.4 全本地化

所有数据在你机器上,没有任何"我点了什么、关心什么"被第三方收集。


🚀 八、后续路线图

按价值排序的扩展方向:

  1. 加美债收益率(10Y / 2Y)—— 是金价 / 股市的核心驱动因子,缺了不全
  2. 加港股 / 中概互联 —— yfinance ^HSI / KWEB,30 分钟即可
  3. 沪金 Au99.99 —— 换 SGE 官方接口或东财,待探查
  4. 周报功能 —— 周日 cron 让 LLM 综合 7 天报告写"本周财经主线"
  5. 历史回测 —— 累积 1 个月数据后,用 LLM 对比"过去 30 天的预警准确率"
  6. 加宏观日历 —— CPI / PMI / NFP 等数据发布日提前预警

🎬 九、写在最后

为什么写这篇:技术博客大多是"理想路径"(按教程走一遍就成),但真实工作中最有用的是"踩坑记录"。这套系统从 0 到生产用了 6 小时,期间踩了 13 个坑,每一个都是一个 stack overflow 没有现成答案的具体问题。把它们串起来记录下来,给同样想搭一套的人省点时间。

值得吗:6 小时 vs 一年几百块订阅费,从纯经济角度未必值。但从"完全可控、信息无偏、数据所有权" 的角度,绝对值。更重要的是这个过程中你掌握了一整套"LLM agent + Python + 配置驱动 + 自动化调度" 的能力,下次想搭别的(个人健康监测、量化策略监控、内容采集 …… 任何"每天定时拉数据 + LLM 分析 + 推送"的场景)几乎是套模板。

这是 LLM-as-tool 的好用例:LLM 不能 24h 监控市场,但能每天定时把一堆原始数据 + 新闻整合成"分析师视角"的简报。这就是它最该干的事 —— 你给它结构化的数据 + 明确的格式约束,它给你高密度的归因和预警。整个系统里 LLM 只是个"专业写手",重活脏活全在 Python 脚本里。


📝 十、关键 takeaway

如果你只能记住三点:

  1. 配置驱动 + 数据/逻辑/调度三层分离 —— 让系统对扩展友好
  2. LLM 指令必须明确,禁止行为要列出来 —— hallucination 不是 bug 而是"指令二义性"的产物
  3. 同样的功能,不同库行为差异巨大 —— 别看名字漂亮就用,先验证它对你的环境是否可用

🎁 完整代码

完整代码(fetch.py / gold.py / stocks.py / oil.py / crypto.py / sources.yaml / SKILL.md)我整理后会放到 GitHub,链接将更新在评论区。如果你想第一时间拿到,点赞 + 收藏 + 关注,更新会推送给你。

如果有任何问题欢迎评论区交流,我会一一回复。


写于 2026-05 本文为原创,转载请注明来源。 如对你有帮助,点赞 / 收藏 / 关注三连支持一下,是我持续输出的动力 🙏