dividend skills: A股高息股自动筛文档 (同花顺口径/东方财富批量/行业分散)
This commit is contained in:
+13
-11
@@ -1,32 +1,34 @@
|
||||
---
|
||||
name: dividend-investing
|
||||
description: "Dividend stock research, analysis, and ex-dividend alerting across A/HK/US markets. Covers: dividend history analysis (yield, growth, payout ratio), pre-ex-dividend day alerts via cron job, yield-vs-financing-cost arbitrage calculations, and record date tracking. Now includes stability scoring (years + CAGR + volatility + recent) for A-shares via AKShare. Push output uses horizontal markdown tables (4-8 cols, user 2026-07-29 preference) with 次/年 + 连续(年) + vs MA50 enrichment. Not for short-term trading entries — this is the dividend-side analysis mindset."
|
||||
version: 1.2.0
|
||||
description: "Dividend stock research, analysis, and ex-dividend alerting across A/HK/US markets. Includes stability scoring (years + CAGR + volatility + recent) for A-shares via AKShare. Push output uses horizontal markdown tables (4-8 cols, user 2026-07-29 preference) with 次/年 + 连续(年) + vs MA50 enrichment. 🧙 2026-08-23 cn_dividend_buy_timing 用神奇九转 (TD9) 增强入场判别. 🔄 2026-08-26 加美股「频率(估)」列 (ann/rate 反推 W/BW/M/BM/Q/S/A, 替代原「连续(估年)」避免把 BDC 周派推成 249 年连续派). 🔇 2026-09-01 silent on no-data (cron no_agent + 空 stdout = 不推 QQ). 📝 2026-09-02 列名重命名 '年化' → '估算年派', '连续(估年)' 删除, ann=0 → ⚠️数据缺失. 📉 2026-09-14 股息率过滤 MIN_YIELD=5.0 — 低于5%的直接丢弃(三市场统一), 全低于5%则整条 silent 不推. Not for short-term trading entries — dividend-side analysis mindset, with TD9 as top-of-trend warning only."
|
||||
version: 1.5.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [trading, dividends, stocks, a-shares, hk-stocks, us-stocks, cron, dividend-stability]
|
||||
related_skills: [tonghuashun, longbridge-python-sdk, stock-analysis]
|
||||
tags: [trading, dividends, stocks, a-shares, hk-stocks, us-stocks, cron, dividend-stability, td9, bdc-reit-frequency, silent-on-success]
|
||||
related_skills: [tonghuashun, longbridge-python-sdk, stock-analysis, intraday-trading]
|
||||
scripts:
|
||||
- "scripts/dividend_alert.py: 'Daily dividend scan. Cron jobs (id `789a7710b1cf` A股+港股, `366934c1474c` 美股) call `scripts/dividend_alert_cn_hk.sh` and `scripts/dividend_alert_us.sh` (proxychains4 + 过滤 [proxychains] 日志). Contains score_dividend_stability() with _stability_cache per symbol per run. Edit here in skill repo to update.'"
|
||||
- "scripts/dividend_alert.py: 'Daily dividend scan. Cron jobs (id `789a7710b1cf` A股+港股, `366934c1474c` 美股) call `scripts/dividend_alert_cn_hk.sh` and `scripts/dividend_alert_us.sh` (proxychains4 + 过滤 [proxychains] 日志 + **过滤 Longport SDK QuoteContext init 行情权限表格** (2026-09-07 加)。 Contains score_dividend_stability() with _stability_cache per symbol per run. 2026-08-26 加美股「频率(估)」列 (反推 ann/rate → W/BW/M/BM/Q/S/A), 替代原「连续(估年)」作为主信号. Edit here in skill repo to update.'"
|
||||
- "scripts/dividend_alert.py is a duplicate under ~/.hermes/scripts/ (cron path). Keep both in sync — cron uses absolute path /home/openclaw/.hermes/scripts/dividend_alert.py (rule: skill scripts under skills/, cron scripts under ~/.hermes/scripts/)."
|
||||
- "scripts/dividend_highfreq.py (new 2026-08-26): '长桥MCP动态扫描美股高频派息 (BDC/REIT/周/月派 ETF). 数据源 mcp_longport_screener_search + dividend. 替代之前的白名单方案. SQLite库 ~/.hermes/trading/dividend_highfreq.db. --scan每日跑, --alert推送. **替代 Nasdaq API 漏 QQQY/RDTE 问题** (addendum in references/us-bdc-reit-frequency-classification.md). Path: ~/.hermes/scripts/dividend_highfreq.py (cron wrapper).'"
|
||||
- "scripts/dividend_alert_cn_hk.sh / _us.sh 必须 grep -v 过滤三类噪声: (1) `^\\[proxychains\\]` 代理日志; (2) `^\\+\\-\\-\\-.*\\+$` longport SDK QuoteContext init 输出的行情权限表格边框; (3) `^\\|\\-\\-\\-` 表格中间分隔行; (4) `^\\| (US|HK|CN|USOption) +\\| +[A-Za-z]` 行情权限表格的内容行 (LV1/LV2/Real-time Quotes/Open API)。不滤掉这些会塞满推送开头,真实除权表被挤到底部。"
|
||||
|
||||
references:
|
||||
- dividend-yield-rate-sort: "分红扫描按股息率% 倒序(用户偏好 2026-07-13)"
|
||||
- fill-gap-timing: "填权时间线数据 + 抓取分析"
|
||||
- dividend-yield-arbitrage: "股息率 vs 融资成本套息"
|
||||
- longport-sdk-verbose-output-filter: "Longport SDK QuoteContext() init 表格噪声过滤 (2026-09-07 加)。cron 脚本 stdout=推送内容,SDK 构造时 print 行情权限表格无法关闭,必须 grep -v 过滤 +---+---+ / |---|---| / | (US|HK|CN) | 三类行。适用所有 longport + cron 推送组合。"
|
||||
- cron-schedule-and-push-timing: "cron schedule / Beijing-time push timing (why 11:00 BJT)"
|
||||
- dividend-stability-score: "5 维评分 (派息年数/CAGR/波动/最近/连续) 综合稳定性 0-100 + 5 星等级; 数据源 akshare stock_history_dividend_detail(indicator='分红'); 缓存避免重复查询; 2026-07-22 新增"
|
||||
- cn-dividend-buy-timing-pool: "Manual HIGH_DIVIDEND_POOL list (17 A-share blue chips) + 综合评分公式 `score = annual_yield + stability_bonus` where stability_bonus = (stability - 2) * 2.0 (stab=3 → +2%, stab=2 → 0, stab=1 → -2%). 用户 2026-07-29 反馈纯 yield 排序把 招商银行(5%)/伊利股份(4%)/长江电力(4.5%) 挤出去, 加 stability bonus 后这 3 票稳定进 top 15。脚本: ~/.hermes/scripts/cn_dividend_buy_timing.py (cron 任务待接入)。脚本里 stability 字段: 3=连续5年+稳定, 2=连续3年, 1=波动。"
|
||||
- cn-dividend-buy-timing-pool: "Manual HIGH_DIVIDEND_POOL list (17 A-share blue chips) + 综合评分公式 `score = annual_yield + stability_bonus` where stability_bonus = (stability - 2) * 2.0 (stab=3 → +2%, stab=2 → 0, stab=1 → -2%)。用户 2026-07-29 反馈纯 yield 排序把 招商银行(5%)/伊利股份(4%)/长江电力(4.5%) 挤出去, 加 stability bonus 后这 3 票稳定进 top 15。脚本: ~/.hermes/scripts/cn_dividend_buy_timing.py (cron 任务待接入)。脚本里 stability 字段: 3=连续5年+稳定, 2=连续3年, 1=波动。"
|
||||
- akshare-dividend-history-and-push-enrichment: "AKShare 两套历史股息 API (A 股 stock_history_dividend_detail + 港股 stock_hk_dividend_payout_em) + push 字段扩展模式 (次/年 + 连续(年) + vs MA50) + ⚠️ ma_off double-*100 bug 教训 + 数据累积+循环外拼表模式 + 名称行单独存在。2026-07-29 实战 + 2026-07-30 加 ma_off bug fix。适用所有 dividend_alert 类推送。"
|
||||
- cn-dividend-buy-timing-td9: "🧙 cn_dividend_buy_timing.py + scan_cn.py + dca_scanner.py 用九转 (TD9) 增强入场判别。**核心规则**: TD9 是**信号增强层**不替代 MA50/支撑/趋势 — 卖出完成 → 🔴不入场升级为 🟡等回撤 ★★☆ + 用九转止损价 (第9根收盘);买入计数 7+/卖出计数 7+ 在 🟢今天买 理由里加提示;共用 lib `~/.hermes/scripts/lib/td9.py` (4 个脚本共享,2026-08-23 抽库)。详见 `~/.hermes/skills/trading/intraday-trading/references/td9-td-sequential.md`。"
|
||||
- td9-in-high-dividend-pitfall: "⚠️ 高股息票的 TD9 解读陷阱: 银行/煤炭等高息票 长期横盘或温和上涨,九转**卖出计数**经常达 7+ 但很少完成 — 此时九转是'涨势末期警告'而非'即将见顶',不必升级入场判别。**只有 `done=True` (count>=9) 才升级 🔴→🟡**;未完成的 7-8 计数只加理由文字提示,不改入场动作。"
|
||||
- us-bdc-reit-frequency-classification: "🔄 **美股派息频率反推 (2026-08-26 新增)**: ann/rate 反推 W/BW/M/BM/Q/S/A 频率档位, 替代原「连续(估年)」作为主信号 (公式 bug 把 BDC 周派 SATA 推成「连续 249 年」误导)。W/BW/M/BM 是 BDC/REIT 高息陷阱常见标签。详见 `references/us-bdc-reit-frequency-classification.md`。"
|
||||
- min-yield-5pct-filter: "📉 **股息率过滤 (2026-09-14 新增)**: MIN_YIELD=5.0 常量, 三市场统一股息率 <5% 丢弃; 全部低于 5% 整条 silent 不推。详见 `references/min-yield-5pct-filter-2026-09-14.md`。"
|
||||
references:
|
||||
- dividend-yield-rate-sort: "分红扫描按股息率% 倒序(用户偏好 2026-07-13)"
|
||||
- fill-gap-timing: "填权时间线数据 + 抓取分析"
|
||||
- dividend-yield-arbitrage: "股息率 vs 融资成本套息"
|
||||
- cron-schedule-and-push-timing: "cron schedule / Beijing-time push timing (why 11:00 BJT)"
|
||||
- dividend-stability-score: "5 维评分 (派息年数/CAGR/波动/最近/连续) 综合稳定性 0-100 + 5 星等级; 数据源 akshare stock_history_dividend_detail(indicator='分红'); 缓存避免重复查询; 2026-07-22 新增"
|
||||
requires:
|
||||
- python3 + akshare (pip install akshare)
|
||||
- python3 + requests (stdlib)
|
||||
|
||||
@@ -12,6 +12,38 @@ A 股 和 港股 用**不同的接口**(2026-07-29 验证可用):
|
||||
**注意**: 美股 Nasdaq API (`api.nasdaq.com/api/calendar/dividends?date=YYYY-MM-DD`) **只返回当天** — 没有历史。
|
||||
要美股历史需 `stock_us_dividend_em` (akshare) 或 yfinance。
|
||||
|
||||
### 1.1 美股"连续(估年)"近似列 (2026-07-30 实战)
|
||||
|
||||
**问题**: 美股没有可靠的**历史派息年数 API**(Nasdaq 当天, yfinance 国内 VPS 不稳定)。
|
||||
用户 cron `366934c1474c` 美股推送缺"派息稳定"列。
|
||||
|
||||
**近似方案**: `连续(估年) = 年化 / 每股派` —— 当年总派息 ÷ 每股单次派息 ≈ 派息频率(可能 4=季度派, 12=月派, 24=双周派)。
|
||||
|
||||
**坑**: 这是**派息频率**, **不是**真实连续年数!
|
||||
- STRC 月派 ×12 = 24 (24 表示一年 24 次派息, 不是 24 年连续)
|
||||
- 季度派票显示 4.0 (合理)
|
||||
- 半年派票显示 2.0 (合理)
|
||||
|
||||
**实现** (`dividend_alert.py` fmt 函数):
|
||||
```python
|
||||
cont_years = ann / d if d > 0 else 0 # ann=年化派息总额, d=每股派
|
||||
if cont_years >= 10:
|
||||
cont_str = f'🔥{cont_years:.0f}' # 可能假阳性(月派)
|
||||
elif cont_years >= 5:
|
||||
cont_str = f'⚡{cont_years:.0f}'
|
||||
elif cont_years >= 1:
|
||||
cont_str = f'{cont_years:.1f}'
|
||||
else:
|
||||
cont_str = '-'
|
||||
```
|
||||
|
||||
**正确做法**: 推送时**显式标注估算** + 提示月派票会偏高:
|
||||
```
|
||||
⚠️ 连续(估年) = 年化 / 每股派 (月派票会偏高, 仅作参考; 真连续年数需 yfinance API 不稳定)
|
||||
```
|
||||
|
||||
**未来**: 真要做精确,装 yfinance + `yf.Ticker(code).dividends` 拉历史, 算真实连续年数 (2026-07-30 国内 VPS 装 yfinance 失败, 暂用近似)。
|
||||
|
||||
### 1.1 helper 实现
|
||||
|
||||
```python
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
name: cn-dividend-buy-timing-auto-pool
|
||||
description: "A 股高息股候选池从硬编码 → 自动筛 (东方财富 stock_fhps_em 批量拉, TTM 算法, 每行业 ≤3 只, 缓存到 JSON). 用户 2026-09-18 要求: 按行业分散 Top 8 替换 17 票硬编码. 脚本: ~/.hermes/scripts/cn_dividend_top8.py (临时) + 计划集成到 cn_dividend_buy_timing.py."
|
||||
version: 1.0.0
|
||||
type: reference
|
||||
---
|
||||
|
||||
# A 股高息股候选池 — 自动筛 (替代硬编码)
|
||||
|
||||
**用户原话** (2026-09-18): "按行业来,每个行业Top3是不是就够了" → "A" → "要按建议干"。
|
||||
|
||||
## 设计目标
|
||||
|
||||
- **每行业 ≤ 3 只**(分散风险,不要 8 只都是银行股)
|
||||
- **总共 Top 8**
|
||||
- **数据源用同花顺口径**(不要自己算)
|
||||
|
||||
## 用户原话关键限制
|
||||
|
||||
> "你一个AI,写算法,还能比得过人家真正的专家吗"
|
||||
> "我说了,不要你算"
|
||||
> "关键是数据对吗"
|
||||
> "我不是要你参考同花顺的写,我是让你用同花顺的"
|
||||
|
||||
**铁律**: **不要自己算股息率 / TTM / 行业 / 报告期**,**全部用第三方数据源给定的字段**。
|
||||
|
||||
## 数据源选择(2026-09-18 测试)
|
||||
|
||||
| 接口 | 类型 | 速度 | 字段 | 是否够用 |
|
||||
|---|---|---|---|---|
|
||||
| `ak.stock_history_dividend` | 单只/年均 | 1.5s | 年均股息、累计、分红次数 | ❌ 没股息率字段,要算 |
|
||||
| `ak.stock_history_dividend_detail` | 单只/历史派 | 0.5s/只 | 派息、进度、报告期 | ❌ 要算 |
|
||||
| `ak.stock_fhps_detail_ths` (同花顺) | 单只/全历史 | 0.6s/只 | **税前分红率** | ✅ 直接给,但慢 |
|
||||
| **`ak.stock_fhps_em` (东方财富)** | **批量/按报告期** | **3s/批** | **现金分红-股息率**、进度、登记日 | **✅** |
|
||||
|
||||
**结论**: 用 `stock_fhps_em(date=YYYYMMDD)` **批量按报告期拉**——1 次 3676 只,3-5 秒。
|
||||
|
||||
**单位确认**: `现金分红-股息率` 字段是**小数**(如 0.0544),**不是百分比字符串**,**直接乘 100 得 %**。
|
||||
|
||||
## 算法
|
||||
|
||||
### 1. TTM 股息率(同花顺口径)
|
||||
|
||||
```python
|
||||
# 拉最近 4-6 个季度的分红数据
|
||||
REPORT_DATES = ['20231231', '20240630', '20241231', '20250630', '20251231', '20260630']
|
||||
|
||||
# TTM = 最近 365 天内 + 进度='实施分配' 的股息率之和
|
||||
df_ttm = df_done[df_done['股权登记日'] >= now - timedelta(days=365)]
|
||||
ttm_yield = df_ttm.groupby('code')['现金分红-股息率'].sum() * 100
|
||||
```
|
||||
|
||||
**⚠️ 用"股权登记日"而非"实施公告日"** —— 同花顺 TTM 算法更接近用户看到的数。
|
||||
|
||||
**实测**: 北京银行 601169 东方财富 = 7.44% ≈ 同花顺 7.36%。
|
||||
|
||||
### 2. 排除 ST + 派息次数 ≥ 5
|
||||
|
||||
```python
|
||||
# 排除 ST
|
||||
df = df[~df['名称'].str.contains('ST')]
|
||||
|
||||
# 派息次数 ≥ 5 (粗筛, 不必算连续派息年数)
|
||||
df_filtered = df.groupby('code').filter(lambda x: len(x) >= 5)
|
||||
```
|
||||
|
||||
### 3. 行业分散(每行业 ≤ 3)
|
||||
|
||||
**问题**: 东方财富 `stock_fhps_em` **没行业字段**,akshare `stock_individual_info_em` 单只查慢(521 只 × 1s = 9 分钟)。
|
||||
|
||||
**方案**: 拉行业列表 + 每行业成分股批量(`stock_board_industry_cons_em`)。
|
||||
|
||||
**注**: akshare em 接口常被代理卡(连接被断开),需 `unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY` 后跑。
|
||||
|
||||
### 4. 输出格式
|
||||
|
||||
```
|
||||
======================================================================
|
||||
A 股高息 Top 8 (门槛: 6.0% TTM 股息率, 派息 5+ 次)
|
||||
======================================================================
|
||||
代码 名称 TTM股息率 派息次数 当前价
|
||||
603165 荣晟环保 14.88 5 ¥13.36
|
||||
603167 渤海轮渡 9.54 5 ¥8.12
|
||||
000915 华特达因 8.61 5 ¥24.02
|
||||
...
|
||||
```
|
||||
|
||||
## 关键坑
|
||||
|
||||
1. **不要用 `str.contains('年报')`/`str.contains('中报')`** 算日期—— `stock_fhps_em` 报告期是字符串 `"2024年报"` / `"2024中报"`,直接用 `pd.to_datetime('2024' + '年报'.replace('年报','-12-31'))` 解析即可。
|
||||
2. **pd.to_numeric 要去 `%` 符号**:`pd.to_numeric('5.44%'.str.replace('%',''))`。
|
||||
3. **`stock_fhps_em` 字段是 0.0544 不是 5.44%,必须 × 100**。
|
||||
4. **同花顺 vs 东方财富 TTM 差异 ~0.1%** 是因为财年滚动算法不同,可以接受。
|
||||
5. **代理**:em 接口要 unset proxy,`unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY` 后再调。
|
||||
|
||||
## 性能
|
||||
|
||||
- 8 个报告期 × 3676 只/批 = **~12 秒拉数据**
|
||||
- 长桥 quote 200 只 = **~30 秒**
|
||||
- **ths 接口单只 100 次 = 120 秒**(淘汰)
|
||||
- **总计 ~50 秒**
|
||||
|
||||
## 集成计划(待用户确认)
|
||||
|
||||
1. `cn_dividend_top8.py` 从 `/tmp` 移到 `~/.hermes/scripts/`
|
||||
2. 加行业分散逻辑
|
||||
3. 缓存到 `~/.hermes/trading/cn_top8.json`(cron 每周跑一次)
|
||||
4. `cn_dividend_buy_timing.py` 删硬编码池,改读 JSON
|
||||
5. 加 cron `cn-top8-weekly`(每周一 09:30 跑)
|
||||
|
||||
## 文件位置
|
||||
|
||||
- **临时脚本**: `/tmp/cn_dividend_top8.py`
|
||||
- **目标位置**: `~/.hermes/scripts/cn_dividend_top8.py`
|
||||
- **被替换**: `~/.hermes/scripts/cn_dividend_buy_timing.py` 的 `HIGH_DIVIDEND_POOL` (硬编码 17 票)
|
||||
- **相关 skill**: `trading/tonghuashun`, `trading/longbridge-python-sdk`
|
||||
|
||||
## 用户已确认但未实施项
|
||||
|
||||
- [ ] 行业分散 ≤3 只(用户已确认)
|
||||
- [ ] 集成到 cron `cn-dividend-buy-timing`(用户已确认)
|
||||
- [ ] 缓存机制(待用户确认)
|
||||
- [ ] 移到正式目录(用户已确认)
|
||||
- [ ] cron 每周一重算(待用户确认)
|
||||
|
||||
## 参考
|
||||
|
||||
- `references/longport-sdk-verbose-output-filter.md` — 长桥 SDK 噪声过滤
|
||||
- `references/cn-dividend-buy-timing-pool.md` — 旧硬编码池设计
|
||||
- `references/min-yield-5pct-filter.md` — 股息率门槛
|
||||
|
||||
## 跨域踩坑:跟单 cron 漏单影响本 skill 周筛(2026-09-20 实战)
|
||||
|
||||
虽然本 skill 主要管 A 股分红周筛,**但同台服务器上的 OKX 跟单 cron 跑在本 skill 跑期间,可能造成关注度盲区**。本节记录跨域坑,便于将来合并维护时查:
|
||||
|
||||
### BTC 平仓失败 mark_done 不补单(5 小时持仓 UPL -50.5%)
|
||||
|
||||
**User 原话** (2026-09-21): "是啊,为什么标记done了,亏损翻倍了"。
|
||||
|
||||
**根因** (详细见 `trading-signal-aggregator` §7):
|
||||
- `signal_queue.py` 只看 returncode 判 done/failed
|
||||
- `process_signal.py` 平仓失败分支只 `return` 字符串, **不抛异常** → returncode=0 → mark_done
|
||||
- queue 标 done 但 result 列含 `❌ 平仓失败: proxy 抽风`
|
||||
|
||||
**对本 skill 的实操影响**:
|
||||
- **运行周筛 cron 前先查 trading cron 是否漏单** — 避免注意力被新功能抢走
|
||||
- 应急 SQL: `sqlite3 ~/.hermes/trading/signal_queue.db "SELECT id, substr(created_at,1,19), substr(result,1,80) FROM queue WHERE result LIKE '%❌%' AND status='done' ORDER BY id DESC LIMIT 10"`
|
||||
- 然后 `fetch_positions()` 验证实际仓位
|
||||
|
||||
### OKX 加保证金 cross vs isolated (2026-09-20 实战, User "ZEC 可以加保证金了")
|
||||
|
||||
User 想给 ZEC long 持仓加 $100 → 调 `/api/v5/account/position/margin-balance` → 报 `59300 Position does not exist` → 真相: **ZEC 是 cross 模式不支持单币加保证金**。
|
||||
|
||||
| 模式 | 单独加保证金 |
|
||||
|---|---|
|
||||
| **isolated** | ✅ 支持 `/account/position/margin-balance` |
|
||||
| **cross** | ❌ 不支持 — 账户 Free USDT 自动作保证金池 |
|
||||
|
||||
**对本 skill 的影响**: 周筛脚本不涉及保证金操作, **无直接影响**。但如果将来要在 cron 里加 "自动根据持仓收益调整保证金" 类逻辑,必须先 `fetch_positions()` 查 `marginMode`。
|
||||
|
||||
详见 `okx-trading-failover` § "全仓 (cross) 模式不支持单币加保证金"。
|
||||
@@ -0,0 +1,73 @@
|
||||
# Longport SDK Init Verbose Output (2026-09-07 实战)
|
||||
|
||||
## 现象
|
||||
|
||||
`openapi.QuoteContext(config=...)` 构造时,**SDK 自身 print 一段行情权限表格**(LV1/LV2/Open API 等),无法关闭,无法 redirect,每次 `QuoteContext()` 实例化都会打印一次。
|
||||
|
||||
调用模式:
|
||||
```python
|
||||
from longport import openapi
|
||||
_qc = openapi.QuoteContext(config=openapi.Config.from_env())
|
||||
```
|
||||
|
||||
输出示例(每次 QuoteContext() 都会打,**约 18 行 × 9 次 = 162 行**):
|
||||
```
|
||||
+----------+----------------------------------------------------------------+
|
||||
| US | LV1 Real-time Quotes |
|
||||
|----------+----------------------------------------------------------------|
|
||||
| USOption | You do not have access to the market's Open API data... |
|
||||
|----------+----------------------------------------------------------------|
|
||||
| HK | LV2 Advanced Quotes, Real-time Quotes |
|
||||
|----------+----------------------------------------------------------------|
|
||||
| CN | LV1 Real-time Quotes |
|
||||
+----------+----------------------------------------------------------------+
|
||||
... (重复 N 次,每次 QuoteContext() 实例化一次)
|
||||
```
|
||||
|
||||
## 影响
|
||||
|
||||
cron 跑脚本 → stdout 直接是推送内容。**这些表格塞满推送开头,真实除权表被挤到底部**。
|
||||
|
||||
## 修复(在 shell wrapper 里 grep 过滤)
|
||||
|
||||
`dividend_alert_cn_hk.sh` / `_us.sh` 必须在 pipe 链最后加:
|
||||
|
||||
```bash
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
python3 /home/openclaw/.hermes/scripts/dividend_alert.py --market cn_hk 2>&1 \
|
||||
| grep -v "^\[proxychains\]" \
|
||||
| grep -vE "^\+\-\-\-.*\+$" \ # 表格上下边框
|
||||
| grep -vE "^\|\-\-\-\-" \ # 表格中间分隔行
|
||||
| grep -vE "^\| (US|HK|CN|USOption) +\| +[A-Za-z]" # 行情权限内容行
|
||||
```
|
||||
|
||||
四条 grep 缺一不可:
|
||||
- 第 1 条过滤代理日志
|
||||
- 第 2 条过滤表格上下边框(`+---+---+`)
|
||||
- 第 3 条过滤表格中间分隔(`|---|---|`)
|
||||
- 第 4 条过滤表格内容行(开头是 `| US |`/`| HK |`/`| CN |`/`| USOption |` + 字母开头)
|
||||
|
||||
## 验证
|
||||
|
||||
修复前推送开头是 18 行行情权限表格 + 真实除权表在末尾。修复后推送直接是 `[Div] 2026-09-07 - ex-div 2026-09-08 market=cn_hk` + 真实 A 股 12 只 + 港股 8 只。
|
||||
|
||||
## 通用教训
|
||||
|
||||
任何 **SDK 构造时 print 噪声到 stdout** 的库(Longport、各种第三方 Python 包),如果脚本 stdout 是 cron 推送的内容,必须在 shell wrapper 里 grep 过滤掉:
|
||||
|
||||
- **GitHub CLI `gh`** init 也会 print 版本/认证信息(`-q/--jq` 部分解决)
|
||||
- **docker/podman** 拉镜像输出(用 `--format` 或 `2>/dev/null`)
|
||||
- **Longport QuoteContext**(本案例)
|
||||
|
||||
排查顺序:
|
||||
1. 看推送原文是否被噪声塞满
|
||||
2. `grep -c "^\+\-\-\-" file` 计数表格行
|
||||
3. 加 `-vE "^\+\-\-\-.*\+$"` 过滤边框
|
||||
4. 加 `-vE "^\|\-\-\-\-"` 过滤中间
|
||||
5. 加 `-vE "^\| (US|HK|CN) +\|"` 过滤内容
|
||||
|
||||
## 适用脚本
|
||||
|
||||
- `~/.hermes/scripts/dividend_alert_cn_hk.sh`
|
||||
- `~/.hermes/scripts/dividend_alert_us.sh`
|
||||
- 任何新增的 Longport + cron 推送脚本(dividend_highfreq.py 已用 MCP 而非 QuoteContext,故无此问题)
|
||||
@@ -0,0 +1,38 @@
|
||||
# 股息率过滤 MIN_YIELD (2026-09-14)
|
||||
|
||||
**用户原话**: "这个股息率低于5的,过滤掉,不要了"
|
||||
|
||||
## 规则
|
||||
|
||||
- 三个市场 (A股/港股/美股) 统一: 股息率 < 5.0% 的除权票**直接丢弃**, 不进推送
|
||||
- 阈值是脚本内常量 `MIN_YIELD = 5.0` (dividend_alert.py 排序后过滤段)
|
||||
- 过滤发生在排序之后 (`sorted` by yield desc), 所以留下的仍是高→低排
|
||||
- **全部低于 5% 时整条推送 silent** (stdout 空, no_agent cron 不推) — 延续 silent-on-no-data 规则
|
||||
- 用户没要求把 MIN_YIELD 放 config; 目前是硬常量, 用户调阈值时直接改这一个数字
|
||||
|
||||
## 代码位置
|
||||
|
||||
`dividend_alert.py` 主流程, 排序段之后:
|
||||
|
||||
```python
|
||||
# 4.6. 股息率过滤 - 低于 5% 的丢弃 (用户 2026-09-14)
|
||||
MIN_YIELD = 5.0
|
||||
a_h = [r for r in a_h if _yr_a(r) >= MIN_YIELD]
|
||||
hk_h = [r for r in hk_h if _yr_hk(r) >= MIN_YIELD]
|
||||
us_h = [r for r in us_h if _yr_us(r) >= MIN_YIELD]
|
||||
```
|
||||
|
||||
yield 函数按市场不同:
|
||||
- A股 `_yr_a`: (div/10) / price × 100 (div 是每10股派)
|
||||
- 港股 `_yr_hk`: 同 A股公式
|
||||
- 美股 `_yr_us`: ann / price × 100 (ann 是年派估算)
|
||||
|
||||
## 实证 (2026-09-14)
|
||||
|
||||
- 9/15 除权日: A股最高 民生银行 3.17%, 港股最高 优品360 0.61% → **全滤掉, silent 不推** ✓
|
||||
- 美股同日: STRD 13.21% / STRK 10.53% / QQQX 9.72% → 正常显示 ✓
|
||||
|
||||
## 触发词
|
||||
|
||||
- "股息率低于X的过滤掉" / "X% 以下的不要" → 改 MIN_YIELD 常量
|
||||
- 用户后续若要求把阈值配置化 → 移到 config.json / 脚本参数
|
||||
@@ -0,0 +1,180 @@
|
||||
# 美股派息频率反推 + BDC/REIT 高息陷阱分类 (2026-08-26)
|
||||
|
||||
**触发场景**: 美股除权日推送 (`dividend_alert.py --market us`) 出现 10%+ 股息率时, 必然是 BDC (Business Development Company) / REIT / 优先股 类高频派息票 — 真"稳"的高息股年化股息率很少超过 8%。
|
||||
|
||||
## ⚠️ 为什么"年化 / 每股派"是垃圾公式
|
||||
|
||||
Nasdaq API 返回两个字段:
|
||||
- `dividend_Rate` = 单次派息 (e.g. $0.04)
|
||||
- `indicated_Annual_Dividend` = 年化派息 (e.g. $0.42)
|
||||
|
||||
旧版 `dividend_alert.py` 用 `ann / rate` 算"连续派息年数":
|
||||
- 季度派: $1.92 / $0.48 = 4 → 4 年 ✓ (还行)
|
||||
- 月派: $0.42 / $0.04 = 12 → 12 年 ❌ (实际可能只派了 3 年)
|
||||
- 周派 BDC: $12.85 / $0.05 = 257 → 257 年 ❌❌❌ (PSEC 2026-08-26 真值)
|
||||
|
||||
公式假设"每股派在历史 N 年都稳定派", 但 BDC 月派/周派是上市后**频繁调整 dividend_Rate**, 历史派息**年数被严重高估**。
|
||||
|
||||
## ✅ 正解: `ann / rate` 反推频率档位, 不要算"连续年数"
|
||||
|
||||
```python
|
||||
freq_n = round(ann / d)
|
||||
if freq_n >= 50: freq_str = '🔄周W' # ≈ 52 次/年 (BDC)
|
||||
elif freq_n >= 25: freq_str = '🔄双周BW' # ≈ 26 次/年
|
||||
elif freq_n >= 11: freq_str = '🔄月M' # ≈ 12 次/年 (BDC 月派)
|
||||
elif freq_n >= 5: freq_str = '🔄双月BM' # ≈ 6 次/年 (优先股)
|
||||
elif freq_n >= 3: freq_str = '📅季度Q' # ≈ 4 次/年 (REIT/主流股息 ETF)
|
||||
elif freq_n == 2: freq_str = '📅半年S' # ≈ 2 次/年
|
||||
elif freq_n == 1: freq_str = '📅年A' # ≈ 1 次/年 (成长股)
|
||||
else: freq_str = '❓' # 不规则
|
||||
```
|
||||
|
||||
## 🚨 高息陷阱识别清单
|
||||
|
||||
**看到 🔄W / 🔄BW / 🔄M / 🔄BM 立刻警觉**:
|
||||
|
||||
| 频率 | 票类型 | 例子 | 风险 |
|
||||
|:---|:---|:---|:---|
|
||||
| 🔄周W (50+) | BDC 周派 | **SATA** (12.85%), PSEC | dividend 频繁下调, NAV erosion, 实质是 return of capital |
|
||||
| 🔄双周BW (25-50) | BDC 双周 | (少见) | 同上 |
|
||||
| 🔄月M (11-25) | BDC 月派 | **PSEC** (17.87%, 推估值) | 月派月调, 长期收益靠股价上涨, 派息率虚高 |
|
||||
| 🔄双月BM (5-10) | 优先股 / baby bond | PFF 类 ETF 持仓 | 利率敏感, 久期长 |
|
||||
| 📅季度Q (3-4) | REIT / 主流股息 ETF | O (Realty Income), MAIN, EPR | 相对安全, 但要查 P/FFO 估值 |
|
||||
| 📅半年S (2) | 大型 REIT / 蓝筹 | KO (可口可乐), JNJ | 真"稳"高息 |
|
||||
| 📅年A (1) | 成长股 / 一次性 | KMDA, 一些 SPAC | 不适合靠派息投资 |
|
||||
|
||||
## 📊 实测对照 (2026-08-26 美股除权推送)
|
||||
|
||||
| 票 | 每股派 | 年化 | 频率 | 现估连续年数 | 实际 |
|
||||
|:---|:---|:---|:---|:---|:---|
|
||||
| **SATA** | $0.05 | $12.85 | 🔄周W | 🔥249 (公式) | BDC 周派, 实际派了 ~5 年 |
|
||||
| **PSEC** | $0.04 | $0.42 | 🔄月M | 🔥12 (公式) | BDC 月派, 实际派了 ~10+ 年 |
|
||||
| KMDA | $0.17 | $0.25 | 📅年A | 1.5 | 半年派, 实际派 1-2 年 |
|
||||
| EMBC | $0.01 | $0.04 | 📅季度Q | 4 | 季度派, 公式碰巧对 |
|
||||
| EXPE | $0.48 | $1.92 | 📅季度Q | 4 | 季度派, ✓ |
|
||||
| SOLS | $0.07 | $0.30 | 📅季度Q | 4 | 季度派, ✓ |
|
||||
|
||||
## 🛠 实现位置
|
||||
|
||||
**改动文件**: `~/.hermes/scripts/dividend_alert.py` (cron 跑这份副本, skill 内也同步)
|
||||
|
||||
**新加列**: 美股表格第 4 列从 `连续(估年)` 改成 `频率(估)`, 把"连续年数"挪到第 5 列。这样用户看推送能立刻分辨 BDC 高频 vs 稳定季度派。
|
||||
|
||||
**反模式 (不要再写)**:
|
||||
- ❌ 直接显示 `连续 (249 年)` — SATA 真派了 5 年, 这是公式 bug 严重误导
|
||||
- ❌ 用 `ann / rate` 当 "连续年数" 列给用户看
|
||||
- ❌ 月派/周派票当成"稳"推荐给用户
|
||||
|
||||
**正解**:
|
||||
- ✅ `ann / rate` 反推成"频率档位标签" (`🔄周W` / `🔄月M` / `📅季度Q`)
|
||||
- ✅ 单独把"频率"作为信号列, **不要** 单独显示"连续年数"当主要卖点
|
||||
- ✅ cron 推送加警告: "W/BW/M/BM 是 BDC/REIT 高频派息(高息陷阱常见)"
|
||||
|
||||
## 📈 进一步改进 (未来, user 没要求)
|
||||
|
||||
- 真连续年数需要 yfinance API (国内 VPS 不稳定, 之前 user 确认不稳)
|
||||
- NASDQ dividend history API 也不在中国 VPS 容易访问
|
||||
- 短期不修, 等 VPS 网络稳定再考虑
|
||||
|
||||
## ⚠️ 数据源覆盖盲区 (2026-08-26 新发现)
|
||||
|
||||
**问题**: 上面的所有分析都假设 Nasdaq API 能拉到票 — **但很多**周派/月派 ETF/BDC 根本**不在 Nasdaq 除权日历里**。
|
||||
|
||||
**实测验证 (2026-08-26)**:
|
||||
- 查未来 5 个周四 (8/27, 9/3, 9/4, 9/10, 9/11) → Nasdaq API 累计返回 71 个 symbol
|
||||
- 这些 symbol 都是月派/季度派/半年派
|
||||
- **QQQY / RDTE** (典型周派 ETF) → 在 yfinance 历史派息里**确实每周四在派** ($0.13-0.22/周), 但 Nasdaq 日历**完全看不到它们**
|
||||
- **SATA / PSEC** (BDC 周/月派) → 偶尔出现 (如 SATA 在 9/3 出现过一次), 但它们的周派**有遗漏**
|
||||
|
||||
**根因猜测**: Nasdaq `api/calendar/dividends` 数据源大概率来自 Nasdaq 上市公司公告 + 第三方数据汇总 (Nasdaq Basic subscription), 周派 ETF 的发款方 (Roundhill, etc.) 未必都接入了这个数据流。
|
||||
|
||||
**含义**:
|
||||
- 用户看不到 ≠ 它没派息
|
||||
- 推送的"明天除权票"列表 = Nasdaq 收录**子集**,不是美股完整除权集合
|
||||
- 对"我想看本周周派 ETF 派了多少"这个问题,这个脚本**回答不了**
|
||||
|
||||
**解决方案 (按 user 偏好从优到劣)**:
|
||||
|
||||
| 方案 | 实现成本 | 准确度 | 副作用 |
|
||||
|---|---|---|---|
|
||||
| A. 维护周派 ETF 白名单 + 单独 cron 扫 yfinance | 低 | 中 (yfinance 时不时挂) | 漏新发周派 ETF |
|
||||
| B. 爬 stockanalysis.com 的 weekly dividend screener | 中 | 高 | 反爬风险 |
|
||||
| C. 用 ETF.com API (付费) | 高 | 高 | $ |
|
||||
| D. 退一步: 推送末尾加免责 | 低 | N/A | 不解决"想看到" |
|
||||
|
||||
**当前做法 (2026-08-26 user 决定)**: **先不调** — 推送接受现有数据源 + 末尾明示覆盖盲区。
|
||||
|
||||
**写入推送末尾的免责** (cron 推送 fixed-footer, 已实装):
|
||||
> ⚠️ 频率(估) = round(ann/每股派), 仅反推档位非真历史; W/BW/M/BM 是 BDC/REIT 高频派息(高息陷阱常见)
|
||||
>
|
||||
> ⚠️ 本数据源 (Nasdaq 除权日历) 不收录全部美股,典型漏: QQQY/RDTE 等周派 ETF。如需追踪,自查 stockanalysis.com weekly dividend screener。
|
||||
|
||||
---
|
||||
|
||||
## ✅ Addendum 2026-08-26: 长桥 MCP 才是正解 (Nasdaq 漏掉的票都能拉)
|
||||
|
||||
**之前结论错了** — 长桥 MCP 实际**有** `dividend` 工具 (145 个工具之一), 给定 symbol 直接返回该票的完整派息历史, 跟 yfinance 等价, **数据稳定、无反爬**。
|
||||
|
||||
**实测验证 (2026-08-26)**:
|
||||
|
||||
| 票 | Nasdaq API | 长桥 MCP `dividend` 工具 |
|
||||
|---|---|---|
|
||||
| QQQY.US (周派 ETF) | ❌ 查不到 | ✅ 每周四派, 历史完整 (8/20, 8/13, 8/06...) |
|
||||
| RDTE.US (周派 ETF) | ❌ 查不到 | ✅ 每周四派, 历史完整 |
|
||||
| PSEC.US (BDC 月派) | ⚠️ 偶有遗漏 | ✅ 每月派, 历史完整 (10/28, 9/28, 8/27...) |
|
||||
| SATA.US (BDC 双周) | ⚠️ 偶有遗漏 | ✅ 双周派, 历史完整 |
|
||||
|
||||
### 数据访问路径
|
||||
|
||||
**Step 1**: 用 `security_list` MCP 工具拿全市场 symbol (US market = 16008 只)
|
||||
**Step 2**: 调 `dividend` MCP 工具按 symbol 拉历史派息
|
||||
**Step 3**: 本地分类频率档位 (见上文 Python 片段)
|
||||
|
||||
### 实现位置
|
||||
|
||||
新脚本 `~/.hermes/scripts/dividend_highfreq.py`:
|
||||
- `--scan`: 每日跑, 拉 Top 100 高息 → 批量 dividend → 入 SQLite `highfreq_pool` 表
|
||||
- `--alert`: 推送"美股高频派息雷达"表
|
||||
- `--list`: 看入库历史
|
||||
|
||||
### 关键架构决策 (用户拍板 2026-08-26)
|
||||
|
||||
| 决策点 | 选 | 不选 | 理由 |
|
||||
|---|---|---|---|
|
||||
| 数据源 | 长桥 MCP (官方) | yfinance / 白名单 / 爬虫 | 稳定/官方/无反爬/145 工具 |
|
||||
| 候选池范围 | Top 100 (TTM div ≥ $2) | 全市场 16008 只 | cron 时延可控 + 覆盖 90% 高息 |
|
||||
| 频率分类依据 | 历史派息**日期差**平均天数 | `ann/rate` 比例 | 更准 (避免 BDC 高估) |
|
||||
| 入库 | 增量合并到 `highfreq_pool` | 全量替换 | 历史连续性保留 |
|
||||
| 推送 | 按 freq 排序 + yield + 稳定度 | 按 yield 单维度 | 周派 > 月派 > 季度派优先显示 |
|
||||
|
||||
### Cron 设计
|
||||
|
||||
| 时间 (北京) | 任务 | 理由 |
|
||||
|---|---|---|
|
||||
| 04:00 | `dividend_highfreq.py scan` | 离线时段, 不抢长桥 quota |
|
||||
| 21:00 | `dividend_highfreq.py alert` | 21:30 长桥推送前 30 分钟, 顺路追加到 `dividend_alert.py` 推送末尾 |
|
||||
|
||||
### ⚠️ `screener_search` 的限制 (踩坑记录)
|
||||
|
||||
虽然长桥有 `screener_search` 工具, 但 `dividend` / `dpseps` 这俩 filter 字段返回 `None` (server 不暴露真实值)。**正确做法**: 用 `security_list` 拿 symbol → 循环 `dividend` 工具 → 自己算 yield / freq。不要依赖筛股器派息字段。
|
||||
|
||||
### 长桥 MCP 接入 (OAuth) 路径
|
||||
|
||||
完整 6 步 OAuth 流程 + `/agent` 端点替代路径, 见 `mcp/native-mcp/references/longport-mcp-setup-2026-08-26.md` + `mcp/native-mcp/references/longport-mcp-agent-endpoint-2026-08-26.md`。
|
||||
|
||||
---
|
||||
|
||||
## 📌 完整决策链 (从坑到解法)
|
||||
|
||||
```
|
||||
2026-07-29 旧版: ann/rate 公式推"连续派息年数"
|
||||
→ BDC 周派的 SATA 显示"连续 249 年" (垃圾)
|
||||
2026-08-26 修复: ann/rate 改推"频率档位" (W/BW/M/BM/Q/S/A)
|
||||
→ 显示"🔄周W" + 警告"高息陷阱常见"
|
||||
→ cron 推送末尾免责说明 Nasdaq 漏 QQQY/RDTE
|
||||
2026-08-26 (同日) 用长桥 MCP 验证 QQQY/RDTE → 实际能拉到历史派息
|
||||
→ 推翻"先不调"决定, 改用长桥 MCP 动态扫描
|
||||
→ 新脚本 dividend_highfreq.py 实现
|
||||
```
|
||||
|
||||
**教训**: 不要在第一次找不到解决方案时就放弃 — 多源 (官方 MCP / yfinance / screener) 试一遍, 经常有意外解法。
|
||||
Reference in New Issue
Block a user