a-fund-monitor
A股基金净值监控:盘中实时估值 + 盘后实际净值,定时推送到 Telegram。
Install
npx skills add https://github.com/aAAaqwq/AGI-Super-Team/tree/main/skills/a-fund-monitor
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aaaaqwq-agi-super-team@llmmart
git clone https://github.com/aAAaqwq/AGI-Super-Team.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole aaaaqwq/agi-super-team collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
A股基金监控
A股基金净值监控,支持盘中实时估值和盘后实际净值,通过 OpenClaw cron 推送到 Telegram 私聊。
架构
fund_monitor.py (纯 Python,无外部依赖)
├── estimate 模式 → fundgz.1234567.com.cn (盘中实时估值)
└── nav 模式 → api.fund.eastmoney.com/f10/lsjz (收盘净值)
OpenClaw cron (isolated agentTurn)
├── 09:30-19:00 → estimate 模式(收盘后先推估值,净值当晚才公布)
└── 19:00 之后 → nav 模式(收盘实际净值)
└── stdout → delivery announce → Telegram 私聊
关键路径: skills/a-fund-monitor/scripts/(本仓库)
手动执行
# 盘中估值(控制台输出)
python3 scripts/fund_monitor.py estimate
# 收盘净值(控制台输出)
python3 scripts/fund_monitor.py nav
Cron 定时任务
OpenClaw cron,使用 Asia/Shanghai 时区。
| 北京时间 | 模式 | 数据源 |
|---|---|---|
| 10:30 | 盘中估值 | fundgz 接口 |
| 12:30 | 盘中估值 | 同上 |
| 14:30 | 盘中估值 | 同上 |
| 22:00 | 收盘净值 | lsjz 接口(避开 20:30 数据未发布,QDII 滞后 1 天) |
合并为 1 个 cron job: 04a185b2-f8ae-454e-b454-bc1944ca5c00
Cron: 30 10,12,14,22 * * 1-5 (Asia/Shanghai)
Runtime: isolated agentTurn (changqing)
Model: minimax/MiniMax-M3 (fallback: deepseek/deepseek-v4-pro)
Timeout: 300s
Delivery: announce → telegram:8518085684 (Daniel 私聊)
v2.1.1 (2026-06-22) 调整:20:30 → 22:00。多数基金 21:00-22:30 才陆续发布 NAV,旧时间会拿到 4-5 天前的旧数据。同时叠加「今日过滤 + QDII 标记 + HTTP 重试」三重防护。
模式判断逻辑
北京时间 ∈ [09:30, 19:00) → estimate
北京时间 ∈ [19:00, 次日) → nav
投递目标: telegram:8518085684(Daniel Li 私聊),accountId changqing
添加/删除基金
编辑 fund_monitor.py 中的 FUNDS 列表:
FUNDS = [
("003304", "前海开源核心资源A"),
# ... 添加 ("代码", "简称")
]
API 参考
详见 references/eastmoney-api.md。
变更记录
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-06-22 | v2.1.1 | 20:30 → 22:00 避免旧数据;脚本升级为「今日过滤 + QDII 标记 🌏 + HTTP 重试」;cron model 改为 minimax primary(避开 glm-5.1 限流);timeout 提到 300s |
| 2026-05-25 | v2.1.0 | 4 个 cron 合并为 1 个(逗号分隔小时);从 Hermes 迁移到 OpenClaw cron;推送目标改为 Daniel 私聊(8518085684);超时提至 180s;19:00 前走估值避免陈旧数据 |
| 2026-05-22 | v2.0.0 | 初始 Hermes cron 版本 |
Pitfalls
- 超时:14 只基金顺序请求,API 偶发慢速,cron timeout 设为 180s。
- NAV 涨跌幅字段:东方财富 lsjz API 的涨跌幅字段是
JZZZL(净值增长率),不是NAVCHGRT。 - 估值 API 返回 JSONP:
fundgz.1234567.com.cn返回jsonpgz({...});格式,需正则提取 JSON。 - HTTP 请求头:两个 API 都需要
Referer: https://fund.eastmoney.com/和User-Agent,否则可能 403。 - NAV 发布时间不均:多数基金 21:00-22:30 才陆续发布 NAV(QDII 滞后 1 天),脚本已升级为「今日过滤 + QDII 标记 🌏 + HTTP 重试 2 次」。未发布基金显示「⏳ 待发布 (上次: 日期)」,不混进平均。
- 早期 20:30 触发坑:v2.1.0 之前用 20:30 跑 nav 会拿到 4-5 天前的旧数据(脚本只取 items[0] 不过滤日期),已升级为今日过滤。
- QDII 基金估值时间:广发纳斯达克100联接的估值时间显示为 04:00(美股收盘时间),非 A 股 15:00。
- 周末/节假日:cron
1-5仅排除周末,中国法定节假日仍会触发(产出的是上一交易日数据)。
Files (agi-super-team)
-
references
-
eastmoney-api.md 2.8 KB
# 东方财富基金 API 参考 ## 1. 盘中实时估值 API **用途**: 获取交易时段(9:30-15:00)的实时基金估值 ``` GET http://fundgz.1234567.com.cn/js/{fund_code}.js ``` **请求头**: ``` User-Agent: Mozilla/5.0 Referer: https://fund.eastmoney.com/ ``` **响应格式**: JSONP ```javascript jsonpgz({"fundcode":"003304","name":"前海开源沪港深核心资源混合","jzrq":"2026-05-21","dwjz":"5.1670","gsz":"5.3302","gszzl":"3.16","gztime":"2026-05-22 15:00"}); ``` **字段说明**: | 字段 | 含义 | |------|------| | `fundcode` | 基金代码 | | `name` | 基金全称 | | `jzrq` | 最近净值日期 | | `dwjz` | 最近单位净值 | | `gsz` | 估算净值(实时) | | `gszzl` | 估算涨跌幅(%) | | `gztime` | 估值时间 | **解析**: 正则 `jsonpgz\((.+?)\);` 提取 JSON **注意**: - 非交易时段返回上一交易日收盘时的估值快照 - QDII 基金(如广发纳斯达克100)估值时间显示为美股收盘时间(如 04:00) ## 2. 收盘净值 API **用途**: 获取基金历史净值数据(收盘后更新) ``` GET https://api.fund.eastmoney.com/f10/lsjz?callback=&fundCode={fund_code}&pageIndex=1&pageSize=1 ``` **请求头**: ``` User-Agent: Mozilla/5.0 Referer: https://fund.eastmoney.com/ ``` **注意**: `callback=` 留空则返回纯 JSON(非 JSONP) **响应格式**: JSON ```json { "Data": { "LSJZList": [ { "DWJZ": "5.1670", "FSRQ": "2026-05-21", "JZZZL": "-1.20", "LJJZ": "5.1970", "SGZT": "开放申购", "SHZT": "开放赎回" } ] } } ``` **关键字段**: | 字段 | 含义 | |------|------| | `DWJZ` | 单位净值 | | `FSRQ` | 净值日期 | | `JZZZL` | 净值增长率(日涨跌幅%) ⚠️ 不是 NAVCHGRT | | `LJJZ` | 累计净值 | ## 3. 基金搜索 API **用途**: 通过名称查找基金代码 ``` GET https://fundsuggest.eastmoney.com/FundSearch/api/FundSearchAPI.ashx?m=1&key={keyword} ``` **响应**: JSON,`Datas` 数组中每项含 `CODE` 和 `NAME` ## 4. 基金代码速查 当前监控的 14 只基金: | 代码 | 简称 | 备注 | |------|------|------| | 003304 | 前海开源沪港深核心资源混合A | | | 015790 | 永赢高端装备智选混合发起C | | | 015508 | 兴业中证500指数增强C | | | 000979 | 景顺长城沪港深精选股票A | | | 270042 | 广发纳斯达克100ETF联接人民币(QDII)A | QDII,净值延迟 | | 519771 | 交银优择回报灵活配置混合C | | | 014418 | 西部利得CES芯片指数增强A | | | 012414 | 招商中证白酒指数(LOF)C | | | 002943 | 广发多因子混合 | | | 025209 | 永赢先锋半导体智选混合发起C | | | 000217 | 华安黄金ETF联接C | | | 012768 | 华夏中证动漫游戏ETF发起式联接A | | | 025857 | 华夏中证电网设备主题ETF发起式联接C | | | 026073 | 广发研究智选混合C | |
-
-
scripts
-
fund_estimate.sh 66 B
#!/bin/bash cd "$(dirname "$0")" python3 fund_monitor.py estimate -
fund_monitor.py 8.8 KB
#!/usr/bin/env python3 """ A股基金监控脚本(v3 · 2026-07-21) ⚠️ 历史: - v1: 东方财富 fundgz.1234567.com.cn 盘中估值 API(2026-07 下线) - v2: 统一走 NAV(盘中无估值) - v3: 发现天天基金新接口 FundValuationLast(2026-07-21 发现于 leek-fund #677) 数据源(v3 双源混合): - 主(估值):fundcomapi.tiantianfunds.com/mm/newCore/FundValuationLast - 主(NAV):api.fund.eastmoney.com/f10/lsjz - 备:danjuanfunds.com/djapi/fund/growth 显示逻辑: - 盘中(09:30-15:00 北京工作日):优先显示估值;无估值的显示上一交易日 NAV - 收盘后 / 非交易日:显示最新 NAV """ import sys import json import time import urllib.request import urllib.parse from datetime import datetime, timedelta from zoneinfo import ZoneInfo BEIJING_TZ = ZoneInfo("Asia/Shanghai") FUNDS = [ ("003304", "前海开源核心资源A", False), ("015790", "永赢高端装备C", False), ("015508", "兴业中证500增强C", False), ("000979", "景顺沪港深精选A", True), ("270042", "广发纳斯达克100联接A", True), ("519771", "交银优择回报C", False), ("014418", "西部利得芯片增强A", False), ("012414", "招商白酒LOF C", False), ("002943", "广发多因子混合", False), ("025209", "永赢先锋半导体C", False), ("000217", "华安黄金ETF联接C", True), ("012768", "华夏动漫游戏ETF联接A", False), ("025857", "华夏电网设备ETF联接C", False), ("026073", "广发研究智选C", False), ] FIRE_EMOJI = "🔥" QDII_EMOJI = "🌏" EST_EMOJI = "📊" # 估值标记 def fetch_with_retry(url, retries=2, timeout=10, headers=None): default_headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", "Referer": "https://h5.1234567.com.cn/", } if headers: default_headers.update(headers) for i in range(retries + 1): try: req = urllib.request.Request(url, headers=default_headers) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8") except Exception: if i < retries: time.sleep(2) continue return None return None def fetch_valuations(codes): """批量拉估值(一次请求所有基金)""" codes_str = ",".join(codes) url = ( "https://fundcomapi.tiantianfunds.com/mm/newCore/FundValuationLast?" + urllib.parse.urlencode({ "FCODES": codes_str, "FIELDS": "FCODE,SHORTNAME,GSZZL,GSZ,GSZTIME,FSRQ,DWJZ", }) ) text = fetch_with_retry(url, retries=2, timeout=10) if not text: return {} try: data = json.loads(text) result = {} for item in (data.get("data") or []): code = item.get("FCODE") if code: result[code] = item return result except Exception: return {} def fetch_nav_eastmoney(code): url = f"https://api.fund.eastmoney.com/f10/lsjz?callback=&fundCode={code}&pageIndex=1&pageSize=3" text = fetch_with_retry(url, retries=2, headers={"Referer": "https://fund.eastmoney.com/"}) if not text: return None try: data = json.loads(text) items = data.get("Data", {}).get("LSJZList", []) if items: return items[0] except Exception: pass return None def fetch_nav_danjuan(code): url = f"https://danjuanfunds.com/djapi/fund/growth/{{{{{code}}}}}" text = fetch_with_retry(url, retries=1, timeout=8) if not text: return None try: data = json.loads(text) records = data.get("data", {}).get("fund_nav_growth", []) if records: last = records[-1] return { "FSRQ": last.get("date", ""), "DWJZ": last.get("nav", ""), "JZZZL": last.get("percentage", "0"), } except Exception: return None return None def fetch_nav(code): item = fetch_nav_eastmoney(code) if item and item.get("DWJZ"): return item return fetch_nav_danjuan(code) def is_weekday_beijing(): return datetime.now(BEIJING_TZ).weekday() < 5 def is_intraday_beijing(): """北京时间盘中(工作日 09:30-15:00)""" now = datetime.now(BEIJING_TZ) if now.weekday() >= 5: return False # 09:30-15:00 minutes = now.hour * 60 + now.minute return 9 * 60 + 30 <= minutes <= 15 * 60 def get_recent_trading_day(today): d = today while d.weekday() >= 5: d -= timedelta(days=1) return d def run_report(): now_dt = datetime.now(BEIJING_TZ) now_str = now_dt.strftime("%m-%d %H:%M") today = now_dt.date() today_str = today.strftime("%Y-%m-%d") bj_hour = now_dt.hour intraday = is_intraday_beijing() # 拉估值(批量一次) codes = [f[0] for f in FUNDS] valuations = fetch_valuations(codes) if intraday else {} # Header lines = [] if intraday: lines.append(f"📊 A股基金盘中实时估值 [{now_str} 北京]") lines.append("⏰ 交易时段 · 数据来自天天基金 FundValuationLast") else: # 收盘后或非交易日 d = get_recent_trading_day(today) expected_nav_date = d.strftime("%Y-%m-%d") if bj_hour >= 19: lines.append(f"📊 A股基金收盘净值 [{now_str} 北京]") lines.append(f"📅 截止 {today_str} (北京时间)") else: lines.append(f"📊 A股基金最新净值 [{now_str} 北京]") lines.append(f"⏰ 非交易时段 · 最新可得 NAV: {expected_nav_date}") lines.append("━━━━━━━━━━━━━━") total_change = 0.0 valid_count = 0 has_estimate = 0 has_nav = 0 failed = 0 pending = 0 for code, name, is_qdii in FUNDS: v = valuations.get(code, {}) gsz = v.get("GSZ") gszzl = v.get("GSZZL") gszt = v.get("GSZTIME") # 优先用估值(仅盘中) if intraday and gsz and gszzl is not None: try: pct = float(gszzl) except Exception: pct = 0.0 fire = f" {FIRE_EMOJI}" if abs(pct) >= 3.0 else "" tag = f"{EST_EMOJI}估值" lines.append(f"{name} | {gsz} | {pct:+.2f}%{fire} ({tag})") total_change += pct valid_count += 1 has_estimate += 1 continue # 拉 NAV nav = fetch_nav(code) if not nav: lines.append(f"{name} | ⚠️ 获取失败") failed += 1 pending += 1 continue dwjz = nav.get("DWJZ", "") jzrq = nav.get("FSRQ", "") jzzzl = nav.get("JZZZL", "") try: pct = float(jzzzl) if jzzzl else 0.0 except Exception: pct = 0.0 fire = f" {FIRE_EMOJI}" if abs(pct) >= 3.0 else "" # 判断数据日期 if intraday: # 盘中显示上一交易日 NAV,标注日期 qdii_mark = f" {QDII_EMOJI}QDII" if is_qdii else "" lines.append(f"{name} | {dwjz} | {pct:+.2f}%{fire} (NAV {jzrq[5:]}{qdii_mark})") total_change += pct valid_count += 1 has_nav += 1 else: # 收盘后/非交易日:用 expected date 判断 d = get_recent_trading_day(today) expected = today_str if bj_hour >= 19 else d.strftime("%Y-%m-%d") if jzrq == expected: lines.append(f"{name} | {dwjz} | {pct:+.2f}%{fire}") total_change += pct valid_count += 1 has_nav += 1 else: qdii_mark = f" {QDII_EMOJI}QDII" if is_qdii else "" lines.append(f"{name} | ⏳ 待发布{qdii_mark} (上次: {jzrq})") pending += 1 # Summary lines.append("━━━━━━━━━━━━━━") if valid_count > 0: avg = total_change / valid_count breakdown = [] if has_estimate > 0: breakdown.append(f"{EST_EMOJI}估值: {has_estimate}") if has_nav > 0: breakdown.append(f"NAV: {has_nav}") lines.append(f"📈 平均涨跌: {avg:+.2f}% | ✅ {valid_count}/{len(FUNDS)} ({' | '.join(breakdown)})") if pending > 0: lines.append(f"⏳ 待发布: {pending}/{len(FUNDS)}") else: lines.append(f"⚠️ 暂无数据({pending}/{len(FUNDS)} 待发布)") lines.append("💰 CFO Buffett · AGI Super Team") return "\n".join(lines) if __name__ == "__main__": if not is_weekday_beijing(): bj_now = datetime.now(BEIJING_TZ).strftime("%A %H:%M") print(f"⏭️ 周末跳过(北京当前: {bj_now})") sys.exit(0) print(run_report()) -
fund_nav.sh 61 B
#!/bin/bash cd "$(dirname "$0")" python3 fund_monitor.py nav
-
-
SKILL.md 4.1 KB
--- name: a-fund-monitor description: "A股基金净值监控:盘中实时估值 + 盘后实际净值,定时推送到 Telegram。" version: 2.1.0 triggers: - A股 基金 监控 预测 净值 估值 - fund monitor A-share NAV estimate - 东方财富 基金 API --- # A股基金监控 A股基金净值监控,支持盘中实时估值和盘后实际净值,通过 OpenClaw cron 推送到 Telegram 私聊。 ## 架构 ``` fund_monitor.py (纯 Python,无外部依赖) ├── estimate 模式 → fundgz.1234567.com.cn (盘中实时估值) └── nav 模式 → api.fund.eastmoney.com/f10/lsjz (收盘净值) OpenClaw cron (isolated agentTurn) ├── 09:30-19:00 → estimate 模式(收盘后先推估值,净值当晚才公布) └── 19:00 之后 → nav 模式(收盘实际净值) └── stdout → delivery announce → Telegram 私聊 ``` **关键路径**: `skills/a-fund-monitor/scripts/`(本仓库) ## 手动执行 ```bash # 盘中估值(控制台输出) python3 scripts/fund_monitor.py estimate # 收盘净值(控制台输出) python3 scripts/fund_monitor.py nav ``` ## Cron 定时任务 OpenClaw cron,使用 **Asia/Shanghai** 时区。 | 北京时间 | 模式 | 数据源 | |---------|------|--------| | 10:30 | 盘中估值 | fundgz 接口 | | 12:30 | 盘中估值 | 同上 | | 14:30 | 盘中估值 | 同上 | | **22:00** | 收盘净值 | lsjz 接口(避开 20:30 数据未发布,QDII 滞后 1 天) | **合并为 1 个 cron job**: `04a185b2-f8ae-454e-b454-bc1944ca5c00` ``` Cron: 30 10,12,14,22 * * 1-5 (Asia/Shanghai) Runtime: isolated agentTurn (changqing) Model: minimax/MiniMax-M3 (fallback: deepseek/deepseek-v4-pro) Timeout: 300s Delivery: announce → telegram:8518085684 (Daniel 私聊) ``` **v2.1.1 (2026-06-22) 调整**:20:30 → 22:00。多数基金 21:00-22:30 才陆续发布 NAV,旧时间会拿到 4-5 天前的旧数据。同时叠加「今日过滤 + QDII 标记 + HTTP 重试」三重防护。 ### 模式判断逻辑 ``` 北京时间 ∈ [09:30, 19:00) → estimate 北京时间 ∈ [19:00, 次日) → nav ``` **投递目标**: `telegram:8518085684`(Daniel Li 私聊),accountId `changqing` ## 添加/删除基金 编辑 `fund_monitor.py` 中的 `FUNDS` 列表: ```python FUNDS = [ ("003304", "前海开源核心资源A"), # ... 添加 ("代码", "简称") ] ``` ## API 参考 详见 `references/eastmoney-api.md`。 ## 变更记录 | 日期 | 版本 | 变更 | |------|------|------| | 2026-06-22 | v2.1.1 | 20:30 → 22:00 避免旧数据;脚本升级为「今日过滤 + QDII 标记 🌏 + HTTP 重试」;cron model 改为 minimax primary(避开 glm-5.1 限流);timeout 提到 300s | | 2026-05-25 | v2.1.0 | 4 个 cron 合并为 1 个(逗号分隔小时);从 Hermes 迁移到 OpenClaw cron;推送目标改为 Daniel 私聊(8518085684);超时提至 180s;19:00 前走估值避免陈旧数据 | | 2026-05-22 | v2.0.0 | 初始 Hermes cron 版本 | ## Pitfalls - **超时**:14 只基金顺序请求,API 偶发慢速,cron timeout 设为 180s。 - **NAV 涨跌幅字段**:东方财富 lsjz API 的涨跌幅字段是 `JZZZL`(净值增长率),不是 `NAVCHGRT`。 - **估值 API 返回 JSONP**:`fundgz.1234567.com.cn` 返回 `jsonpgz({...});` 格式,需正则提取 JSON。 - **HTTP 请求头**:两个 API 都需要 `Referer: https://fund.eastmoney.com/` 和 `User-Agent`,否则可能 403。 - **NAV 发布时间不均**:多数基金 21:00-22:30 才陆续发布 NAV(QDII 滞后 1 天),脚本已升级为「今日过滤 + QDII 标记 🌏 + HTTP 重试 2 次」。未发布基金显示「⏳ 待发布 (上次: 日期)」,不混进平均。 - **早期 20:30 触发坑**:v2.1.0 之前用 20:30 跑 nav 会拿到 4-5 天前的旧数据(脚本只取 items[0] 不过滤日期),已升级为今日过滤。 - **QDII 基金估值时间**:广发纳斯达克100联接的估值时间显示为 04:00(美股收盘时间),非 A 股 15:00。 - **周末/节假日**:cron `1-5` 仅排除周末,中国法定节假日仍会触发(产出的是上一交易日数据)。
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.