diff --git a/dividend-investing/SKILL.md b/dividend-investing/SKILL.md index e04c666..5a60175 100644 --- a/dividend-investing/SKILL.md +++ b/dividend-investing/SKILL.md @@ -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) diff --git a/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md b/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md index 58d339e..9961fe6 100644 --- a/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md +++ b/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md @@ -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 diff --git a/dividend-investing/references/cn-dividend-buy-timing-auto-pool.md b/dividend-investing/references/cn-dividend-buy-timing-auto-pool.md new file mode 100644 index 0000000..15cf251 --- /dev/null +++ b/dividend-investing/references/cn-dividend-buy-timing-auto-pool.md @@ -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) 模式不支持单币加保证金"。 diff --git a/dividend-investing/references/longport-sdk-verbose-output-filter.md b/dividend-investing/references/longport-sdk-verbose-output-filter.md new file mode 100644 index 0000000..295d8c5 --- /dev/null +++ b/dividend-investing/references/longport-sdk-verbose-output-filter.md @@ -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,故无此问题) diff --git a/dividend-investing/references/min-yield-5pct-filter-2026-09-14.md b/dividend-investing/references/min-yield-5pct-filter-2026-09-14.md new file mode 100644 index 0000000..b9dfa01 --- /dev/null +++ b/dividend-investing/references/min-yield-5pct-filter-2026-09-14.md @@ -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 / 脚本参数 diff --git a/dividend-investing/references/us-bdc-reit-frequency-classification.md b/dividend-investing/references/us-bdc-reit-frequency-classification.md new file mode 100644 index 0000000..1d999da --- /dev/null +++ b/dividend-investing/references/us-bdc-reit-frequency-classification.md @@ -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) 试一遍, 经常有意外解法。 \ No newline at end of file diff --git a/dividend-scanner/SKILL.md b/dividend-scanner/SKILL.md index 33dae68..801972e 100644 --- a/dividend-scanner/SKILL.md +++ b/dividend-scanner/SKILL.md @@ -10,17 +10,22 @@ trigger: DCA监控、高息扫描、股息率筛选、dividend scan、阶梯买 ``` ~/.hermes/scripts/ -├── dividend_alert.py # 股息登记日前一天提醒(A/HK/US三市场,无Token依赖) -├── dca_scanner.py # 港股/美股扫描(LongPort API) -├── scan_cn.py # A股扫描(LongPort价格+预设股息率) -├── dca_monitor.py # 持仓监控(买入信号),支持 --market=us/hk/cn -├── dca_positions.json # 持仓配置(含阶梯价位、股息率、派息频率) -├── scan_hk.sh # shell包装脚本 +├── lib/ # 公用库 (2026-08-23+) +│ ├── __init__.py +│ └── td9.py # 神奇九转 TD Sequential (4 个脚本共享) +├── dividend_alert.py # 股息登记日前一天提醒(A/HK/US三市场,无Token依赖) +├── cn_dividend_buy_timing.py # A股每日买入时机(11:30 BJT 推送,加权技术指标增强) +├── cn_dividend_buy_timing_cron.sh +├── dca_scanner.py # 港股/美股周扫描(LongPort API) +├── scan_cn.py # A股周扫描(LongPort价格+预设股息率) +├── dca_monitor.py # 持仓监控(买入信号),支持 --market=us/hk/cn +├── dca_positions.json # 持仓配置(含阶梯价位、股息率、派息频率) +├── scan_hk.sh # shell包装脚本 ├── scan_us.sh ├── scan_cn.sh -├── dca_monitor_us.sh # DCA美股监控wrapper(解决no_agent参数问题) -├── rgti_alert.py # RGTI价格提醒 -├── rgti_auto_monitor.py # RGTI半自动做T挂单 +├── dca_monitor_us.sh # DCA美股监控wrapper(解决no_agent参数问题) +├── rgti_alert.py # RGTI价格提醒 +├── rgti_auto_monitor.py # RGTI半自动做T挂单 └── rgti_alert_state.json ``` @@ -50,6 +55,11 @@ trigger: DCA监控、高息扫描、股息率筛选、dividend scan、阶梯买 - `indicated_Annual_Dividend`: 年化分红 - `record_Date`: 登记日(美股登记日通常=除权日) - 优先股/REIT占多数,注意区分 +- **⚠️ 重要坑 (2026-09-01 用户纠正)**: `indicated_Annual_Dividend` (ann) 是 **Nasdaq 估算的"未来一年总派息额"** (单位 USD, **不是百分比, 不是股息率**)。 + - **反推频率档位** = `round(ann / dividend_Rate)` ≈ 一年派几次 → 4=季度(Q) / 12=月(M) / 52=周(W) / 2=半年(S) / 1=年(A) + - **这个数字不是"连续派了几年"**, 旧列名"连续(估年)" = "一年派几次的档位数字" 是误导, **2026-09-01 已改名为"频率(反推)"**, **"年化"已改名为"估算年派"** + - **ann=0 时** = Nasdaq 数据缺失, 频率档位显示 "⚠️数据缺失", 不归入档位 + - 周派票实测 (SATA ann=$12.85, dividend_Rate=$0.05 → 12.85/0.05=257 ≈ 5 trading days × 52 weeks ≈ 260 ✓ 反推逻辑能识别周派) ### 实现要点 - **proxy处理**:服务器在EDT时区,请求国内API需先 `unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY`,否则Python requests走127.0.0.1:7890代理会报ProxyError @@ -96,12 +106,64 @@ trigger: DCA监控、高息扫描、股息率筛选、dividend scan、阶梯买 ## 用户偏好(关键) -1. **吃股息为主,不做短线投机**。当用户问"买点"时,默认用股息率/除权日期/分红增长角度分析,而不是技术面支撑阻力。 +1. **吃股息为主,但入场时机可加技术指标 (2026-08-23 修正)**: 用户原话"高股息扫描的也要结合这个指标(九转)"。**修正**: 之前默认"不做技术面分析"的规则只适用于"分析分红/历史股息率/CAGR"这一类查询。**入场时机判别**(`cn_dividend_buy_timing.py` 的 🟢🟡🔴 决策)可以叠加 TD9 增强(避免在涨势到顶时买入),但仍以股息率 + MA50 + 支撑为底层决策依据,TD9 只是**信号增强层**。 2. **时间显示用北京时间(UTC+8)**,不要用服务器EDT时间。 3. **输出格式**:emoji标记 + 简洁卡片状,数据密集但视觉清爽,≤500字。 +## 🧙 技术指标增强层:神奇九转 (TD Sequential) + +**仅作用于入场时机判别**, 不替代股息率 / MA50 / 支撑等基础条件。 + +### 入场增强规则 (cn_dividend_buy_timing.py) + +| TD9 状态 | 对入场判别的影响 | +|---|---| +| 买入计数 ≥7 (未完成, 跌势末期) | 🟢今天买 理由加 "+ 🧙九转买 N/9 (跌势末期)" | +| 卖出计数 ≥7 (未完成, 涨势末期) | 🟢今天买 理由加 "⚠️🧙九转卖 N/9 (涨势末期,回撤将至)" | +| **卖出完成 (done=True, 涨势到顶)** | 🔴不入场 **升级为 🟡等回撤 ★★☆,止损用九转止损价 (第9根收盘)** | +| 买入计数 <5 或 无九转信号 | 不影响原判别 | + +### 周扫描输出 (scan_cn.py / dca_scanner.py) + +阶梯入场表后加两行: + +``` +| 🧙 九转 | 多 九转买… 6/9 | 空 九转卖✓ 11/9 | - 九转… 0/9 | +| 🧙 止损 | - | 44.70 | - | +``` + +### 公用库抽取 (2026-08-23) + +`td9_levels()` 抽到 `~/.hermes/scripts/lib/td9.py`, **4 个脚本共享**: + +``` +~/.hermes/scripts/lib/td9.py +├── td9_levels(closes, current) → dict or None +└── td9_summary(td9) → str (短字符串用于表格 cell) +``` + +**禁止** 在任何脚本里复制 `td9_levels()` 本地实现 — 修改算法只改 `lib/td9.py`, 4 个脚本自动同步。 + +详见 `intraday-trading/references/td9-td-sequential.md` (算法 + 单元测试 + 输出格式 + 4 脚本用法)。 + ## 关键实现细节 +### `dividend_highfreq.py` — 美股高频派息扫描 (2026-08-26 v1.0 实装 + cron 接入) + +详见 `references/us-dividend-frequency-tier.md` v2 节。设计: `security_list` 拿全 US 市场 → `quote` 批量拿现价 → `dividend` 逐只算频率档位 → yield ≥ 4% 入 SQLite `highfreq_pool` → 按 freq+yield+稳定度推送。**取代了 v1 时期 "Nasdaq 不收周派" 的限制**。 + +**实际跑 (2026-08-26)**: +- 候选池: security_list US 翻页 5 页 → 244 只 (跳过优先股/WARRANT/WTS) +- batch quote 50/批 → 244/244 拿到现价 +- dividend 逐只 (1-4s/只) → 29 只 yield ≥ 4% 且 cv<0.5 入池 +- 总耗时 ~6 分钟 (cron 可接受) + +**Cron job**: `b29af10da8d2` ("美股高频派息雷达"), schedule `30 21 * * 1-5` 北京, no_agent + script 模式, deliver origin. + +**v1.0 实战发现的 YieldMax 票** (CRY/BIOY/XEY/TECY/TSYY 全部 yield>50% 但 cv<0.2 = 真实稳定高息, 不是 bug): 推送时**必须**带"文化娱乐参考, 不构成投资建议" disclaimer + cv 列 + ⭐⭐⭐ 评级让用户自己判断风险. 详见 `native-mcp/references/longport-mcp-tools-pitfalls.md` Pitfall 11. + +**lib/td9.py 接口不匹配 bug (2026-08-26 发现, 待修)**: `td9_levels(closes)` 返回 list, 但 `td9_summary(td9)` 期望 dict → 调用就抛 `TypeError: list indices must be integers or slices, not str`. 修复方向: td9_summary 改成 `td9_summary(td9_list, current_price=None)` 接受 list, 或 td9_levels 改成返 dict. **不要绕过** (`td9_levels` 在 4 个脚本共享, 改签名影响面大). + ### Shell包装脚本(必须) Cron的script字段不能带参数。Python脚本需要参数时,用.sh包装: ```bash @@ -165,24 +227,50 @@ mREIT:NLY/AGNC/TWO/CIM/NYMT/STWD 10. **百度接口港股过滤**:返回数据的 `交易所` 字段,HK=港股、SH/SZ/BJ=A股。注意港股代码前有空格(如 ` 06808`),需要 `strip()`。 11. **除权日与登记日关系**:A股登记日=除权日前一交易日。美股登记日通常=除权日当天。 12. **百度接口日期格式**:`news_trade_notify_dividend_baidu(date='YYYYMMDD')` 参数是纯数字字符串,不要带横杠。支持查未来日期。 +13. **🧙 九转公用库禁止复制粘贴 (2026-08-23)**: 4 个扫描脚本 (`cn_dividend_buy_timing.py` / `scan_cn.py` / `dca_scanner.py` / `daily_t_analysis.py`) 都依赖 `~/.hermes/scripts/lib/td9.py`。**禁止** 在脚本里复制 `td9_levels()` 本地实现 — 修改算法只改 `lib/td9.py`, 4 个脚本自动同步。 +14. **🧙 九转信号增强只用于入场时机, 不替代股息率 (2026-08-23)**: 用户原话"高股息扫描的也要结合九转", **仅限入场时机判别** (`cn_dividend_buy_timing.py` 的 🟢🟡🔴 决策 + 阶梯入场表)。**不要** 把九转叠加到"分析分红历史/CAGR"这一类查询 — 那是基本面分析, 用 AKShare / 东方财富, 不要技术面。 +15. **🧙 九转单元测试先于集成 (2026-08-23)**: 改 `lib/td9.py` 时先在 `/tmp/test_td9.py` 跑 6 个边界 case (连续14跌/14涨/<13根/平盘/振荡/部分跌),确认算法对了再跑全脚本。4 个集成脚本都拉真实 API + proxychains, 慢且依赖重, 调试算法不要每次都拉行情。 +16. **⚠️ Nasdaq `indicated_Annual_Dividend` 字段名翻译坑 (2026-09-01 用户纠正)**: 见上文"美股(Nasdaq API)"段, "年化" 不是"年化股息率" 而是"Nasdaq 估算的年度总派息 (USD)", "连续(估年)" 不是"连续派了几年" 而是"反推频率档位的数字"。**列名误导会让用户以为 4.0 = 派了 4 年, 实际 = 一年派 4 次 (季度派)**。已改名为 "估算年派" + "频率(反推)" + 删除 "连续(估年)" 列 (2026-09-01)。 ## 股息分析工作流(用户问"买点"时) -当用户说"分析XX的买点"时,**先问清楚是吃股息还是做短线**。用户偏好是吃股息,分析框架: +当用户说"分析XX的买点"时,**先区分是吃股息分析还是入场时机判别**: + +### 吃股息分析 (历史/分红/CAGR) + +用户原话指向"看分红能力/可持续性"时, 走这条路径: -### 吃股息分析步骤 1. **查分红历史** → `ak.stock_history_dividend_detail(symbol)` 拿历年数据 2. **算当前股息率** → 每股分红 / 当前股价 3. **算分红增长率** → 5年CAGR、同比变化 4. **查除权时间线** → 已除权还是未除权?最近一次除权日即已错过,等下一波 5. **查盈利预测** → EPS 看分红可持续性(分红率=每股分红/EPS) 6. **查财务健康** → 货币资金、经营现金流(确保有钱分红) -7. **避坑**:不要给 MACD/均线/支撑阻力等技术面分析 +7. **避坑**:不要给 MACD/均线/支撑阻力等纯技术面分析 + +### 入场时机判别 (cn_dividend_buy_timing.py) + +用户原话指向"今天能不能买/什么时候买"时, 走 `cn_dividend_buy_timing.py` 流程: + +1. 拉 K 线 50 根 → 算 MA50 / 支撑 / 趋势 / ATR +2. 跑 **🧙 九转** (`td9_levels`) 作为信号增强层 +3. `recommend_entry()` 综合判别 → 🟢今天买 / 🟡等回撤 / 🔴不入场 +4. **TD9 卖出完成时** → 🔴升级为 🟡等回撤 + 用九转止损价 +5. **TD9 买入计数 ≥7** → 跌势末期加分提示 + +详见上文"🧙 技术指标增强层"小节。 + +### 输出格式(吃股息卡片) -### 输出格式(emoji卡片) ``` 💰 XX年全年分红: XX元/10股 = X.XX元/股 📊 当前价格: XX.XX元 (除权后) 📊 股息率: X.XX% 📈 X年股息CAGR: X.X%/年 ``` + +## 参考资料 + +- `references/us-dividend-frequency-tier.md` — 美股频率档位反推实战 (Nasdaq API ann 字段翻译 + YieldMax 高息陷阱) +- `references/cn-top8-auto-screen.md` — **A 股 Top N 自动筛 v1-v4** (2026-09-17, 已废) — 财年滚动算法 + 含预案 ≈ 同花顺 7.44% (历史记录, 算法验证路径) +- `references/cn-top8-weekly-batch-v5-ths-parallel.md` — **A 股 Top N 自动筛 v5 生产版** (2026-09-19 实装, **当前用这个**) — 东方财富 batch 粗筛 + 同花顺 ths 8 线程并行校验 + JSON 缓存 + 25 秒跑完 Top 8. cron 集成计划 (每周一 + 读缓存). **核心教训**: agent 算法永远追不上同花顺专业团队 → 直接用同花顺接口拿数据 (用户原话 "你一个AI写算法还能比得过专家吗"). diff --git a/dividend-scanner/references/cn-top8-auto-screen.md b/dividend-scanner/references/cn-top8-auto-screen.md new file mode 100644 index 0000000..c88eb0c --- /dev/null +++ b/dividend-scanner/references/cn-top8-auto-screen.md @@ -0,0 +1,98 @@ +# A 股 Top N 自动筛 (替换硬编码候选池) + +**背景**: `cn_dividend_buy_timing.py` 原本用 8 只硬编码候选池 (601288/601398/601088/600023/600585/601006/601939/601658)。用户 2026-09-17 要求改为**自动从全市场筛 Top N**,并按**行业分散**。 + +## 数据源 (优先东方财富, 因为字段已含股息率) + +```python +import akshare as ak + +# 单只拉所有季度分红 (按 report date) +df = ak.stock_fhps_em(date='20251231') # 一次拿 3000+ 只股票 +# 字段: ['代码', '名称', '送转股份-送转总比例', ..., '现金分红-股息率', +# '每股收益', '预案公告日', '股权登记日', '除权除息日', '方案进度', '最新公告日期'] +# 关键: '现金分红-股息率' 是小数 (0.0544 = 5.44%), 不是 "5.44%" 字符串! +``` + +**字段单位坑** (9/17 实测踩到): +- `ak.stock_fhps_em` 的 `现金分红-股息率` 是 **小数** (0.0544) — 不是 "5.44%" 字符串 +- `ak.stock_fhps_detail_ths` 的 `税前分红率` 是 **带 % 的字符串** ("5.44%") — 处理时 `.str.replace('%', '')` 再 `pd.to_numeric` +- `ak.stock_history_dividend_detail` 的 `派息` 是 **每 10 股派多少元** — 不是每股 + +**三种接口对比**: + +| 接口 | 字段 | 单位 | 用法 | +|---|---|---|---| +| `stock_fhps_em(date=)` | `现金分红-股息率` | **小数** (乘 100 = %) | 批量, 3 秒/3000+ 只, 优先 | +| `stock_fhps_detail_ths(symbol=)` | `税前分红率` | 字符串 "%" | 单只, 0.5s/只, TTM 校验 | +| `stock_history_dividend_detail(symbol=)` | `派息` | 元/10 股 | 历史明细, 别自己算股息率 | + +## TTM 算法 + +**用户对照同花顺 APP**: 北京银行显示 7.36%, 我脚本如果只算 "最近 1 年报派息" = 5.44%, 漏了 2024 H1 中报。 + +**TTM 算法**: 拉最近 8 个季度的 `stock_fhps_em` (24 个月), 按 `股权登记日 >= 今天 - 365天` AND `方案进度 == '实施分配'`, 把 `现金分红-股息率` 累加. + +**注意**: 累加前乘 100 (字段是小数, 不是 %) + +```python +df_all = pd.concat([ak.stock_fhps_em(date=d) for d in REPORT_DATES]) +df_all['yld_num'] = pd.to_numeric(df_all['现金分红-股息率'], errors='coerce') * 100 +df_done = df_all[df_all['方案进度'] == '实施分配'].copy() +df_done['reg_date'] = pd.to_datetime(df_done['股权登记日'], errors='coerce') +ttm_start = datetime.now() - timedelta(days=365) +df_ttm = df_done[df_done['reg_date'] >= ttm_start] +ttm_map = df_ttm.groupby('代码')['yld_num'].sum().to_dict() +``` + +## 行业分散 + +**用户原话 (9/17)**: "每个行业 Top3 是不是就够了" + +**实现方案**: 拉行业 → 成分股 → 按行业分组选 Top N (每行业最多 3 只, 总 8 只): + +```python +# akshare 接口 (注意代理): +# ak.stock_board_industry_name_em() → 行业列表 +# ak.stock_board_industry_cons_em(symbol='银行') → 成分股 +``` + +**代理坑**: akshare em 接口常被全局 HTTP_PROXY 拦截, 报错 ConnectionError。 +**修法**: 跑脚本前 `unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY`, 或在 Python 里 `os.environ.pop(...)`。 + +**备选** (更稳): 用 `ak.stock_individual_info_ths(symbol=)` 拿单只股票的行业字段 (`industry` 列), 但**单只 1 秒** → 3000 只 = 50 分钟, 不可行。 + +**当前实现**: 直接按 "已用 TTM 股息率" 排序取前 30 只, **不强行行业分散** (因为 akshare 行业接口代理不稳)。 +**未来优化**: 加本地缓存 (`~/.hermes/cache/cn_industry.json`), 一次拉全, 后续查表。 + +## Cron 集成方案 + +| 项 | 现状 | +|---|---| +| `cn_dividend_buy_timing.py` 硬编码 `HIGH_DIVIDEND_POOL` | 8 只银行/能源 | +| **改进** | 读 `~/.hermes/trading/canary/cn_top8.json` 缓存 | +| **缓存来源** | 每周一 cron 跑 `cn_dividend_top8.py` → 写缓存 | +| **缓存格式** | `[{"code": "601169", "name": "北京银行", "yield_pct": 7.36, ...}, ...]` | + +**脚本路径**: +- 测试版: `/tmp/cn_dividend_top8.py` (9/17 跑通) +- 正式版待移: `~/.hermes/scripts/cn_dividend_top8.py` + +## 关键坑 (9/17 实测) + +1. **`pd.to_numeric(s.str.replace('%', ''))` 处理 None / "--"**: 用 `errors='coerce'` + `.dropna()` +2. **东方财富字段是 0.15 不是 "0.15%"** (小数 vs 百分比字符串) — 乘 100 别漏 +3. **拉 5675 只价格太慢** (>10 分钟) — 粗筛按 "派息次数 × 累计股息" 取 top 200, 只拉这些价格 +4. **代理 env 全局干扰 akshare 国内 API**: 跑脚本前 `unset` 4 个 proxy var +5. **同花顺 vs 自己算的 TTM 不一致**: **永远以同花顺为准**, 自己算只是近似 + +## 实战结果 (9/17) + +| 数据源 | 北京银行 TTM | +|---|---| +| 同花顺 APP | 7.36% | +| `stock_fhps_detail_ths` "实施" 累加 (登记日 TTM) | 5.44% (漏 2024 H1) | +| `stock_fhps_em` TTM (登记日 + 实施, 跨多个 date) | 7.44% ✓ | +| `stock_history_dividend` 自己算 | 5.02-7.36% (单位混淆) | + +**结论**: **`stock_fhps_em` TTM 累加最准**, 接近同花顺 7.36%。 diff --git a/dividend-scanner/references/cn-top8-weekly-batch-v5-ths-parallel.md b/dividend-scanner/references/cn-top8-weekly-batch-v5-ths-parallel.md new file mode 100644 index 0000000..58ea8c0 --- /dev/null +++ b/dividend-scanner/references/cn-top8-weekly-batch-v5-ths-parallel.md @@ -0,0 +1,175 @@ +# A 股 Top N 高息扫描 - v5 周批量化版(2026-09-19 实装) + +## 背景 + +之前的 A 股高息扫描全靠 `cn_dividend_buy_timing.py` 硬编码 8 只候选池(601288/601398/601088/600023/600585/601006/601939/601658),用户要求改为自动从全市场筛 Top N。 + +v1-v4 失败链路: +- v1: 用 akshare `stock_history_dividend_detail` 自己算 TTM → 单位错了(/10 vs 每股) +- v3: 用东方财富 `现金分红-股息率`(小数)→ 字段单位忘了 `* 100`,差 27 倍 +- v4: 改用财年滚动算法 + 含预案 → 算到 5.44%,同花顺显示 7.36%,**还是不准** + +**用户原话**: "你一个AI写算法,还能比得过人家真正的专家吗?... 我也不专业,算出来我也不知道对不对,我也是去看同花顺的" + +**核心教训**: **agent 算法永远追不上同花顺/东方财富专业团队** → **直接用同花顺的接口拿数据**,不自己算。 + +## v5 解决方案(用户接受) + +**两步法**: +1. **东方财富 `stock_fhps_em(date)` 批量粗筛**(3 秒/季,1 拉 3600+ 只,1 次拿全部候选) +2. **同花顺 `stock_fhps_detail_ths(symbol)` 并行校验**(0.5s/只,8 线程并行,100 只 7.7s) + +**总耗时**:~25 秒(东方财富 15s + 同花顺 8s + 长桥价 2s) + +## 关键代码骨架 + +```python +import akshare as ak +from concurrent.futures import ThreadPoolExecutor +import os + +# 0. 关代理(akshare 国内接口被全局 proxy 干扰) +for k in ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY']: + os.environ.pop(k, None) + +REPORT_DATES = ['20231231', '20240630', '20241231', '20250630', '20251231', '20260630'] + +# 1. 东方财富批量粗筛(先 TTM 365 天滚动) +def fetch_eastmoney_batch(): + all_dfs = [] + for date in REPORT_DATES: + df = ak.stock_fhps_em(date=date) # 1 次 3600+ 只 + df['_date'] = date + all_dfs.append(df) + return pd.concat(all_dfs, ignore_index=True) + +def calc_ttm_yield(df_all): + df_all['code'] = df_all['代码'].astype(str).str.zfill(6) + df_all['yld_num'] = pd.to_numeric(df_all['现金分红-股息率'], errors='coerce') * 100 # 字段是小数! + df_done = df_all[df_all['方案进度'] == '实施分配'] + df_done['reg_date'] = pd.to_datetime(df_done['股权登记日'], errors='coerce') + now = datetime.now() + ttm_start = now - timedelta(days=365) + df_ttm = df_done[df_done['reg_date'] >= ttm_start] + return df_ttm.groupby('code')['yld_num'].sum().to_dict(), \ + df_done.groupby('code').size().to_dict() + +# 2. 同花顺并行校验(取粗筛 top 100) +def fetch_ths_one(code): + df = ak.stock_fhps_detail_ths(symbol=code) + # 算连续派息年数(按实施公告日) + if '实施公告日' in df.columns: + df['year'] = pd.to_datetime(df['实施公告日'], errors='coerce').dt.year + years = sorted(df['year'].dropna().unique(), reverse=True) + cont = 1 + for i in range(1, len(years)): + if years[i-1] - years[i] == 1: cont += 1 + else: break + # TTM = 实施公告日 365 天内税前分红率 sum + if '税前分红率' in df.columns: + now = datetime.now() + ttm = now - timedelta(days=365) + recent = df[pd.to_datetime(df['实施公告日'], errors='coerce') >= ttm] + yld_str = recent['税前分红率'].astype(str).str.replace('%', '', regex=False) + ttm_yield = float(pd.to_numeric(yld_str, errors='coerce').dropna().sum()) + else: + ttm_yield = 0 + return code, cont, ttm_yield + +def ths_parallel_verify(candidates, workers=8): + results = {} + with ThreadPoolExecutor(max_workers=workers) as ex: + futures = {ex.submit(fetch_ths_one, code): code for code in candidates} + for fut in as_completed(futures): + code, cont, yld = fut.result() + results[code] = (cont, yld) + return results + +# 3. 过滤 + 排序 +def filter_and_rank(df_all, ttm_map, cnt_map, ths_results, prices): + name_map = df_all.groupby('代码')['名称'].first().to_dict() + rows = [] + for code, em_yield in ttm_map.items(): + name = str(name_map.get(code, '')) + if 'ST' in name or '*ST' in name: continue + if cnt_map.get(code, 0) < 5: continue + cont, ths_yield = ths_results.get(code, (0, 0)) + if cont < 5: continue + # 同花顺优先(它才是权威),没查到用东方财富 + final_yield = ths_yield if ths_yield > 0 else em_yield + rows.append({ + 'code': code, 'name': name, + 'yield_pct': round(final_yield, 2), + 'continuous': cont, + 'div_count': cnt_map.get(code, 0), + 'price': prices.get(code, 0), + 'data_source': 'ths' if ths_yield > 0 else 'em', + }) + rows.sort(key=lambda x: -x['yield_pct']) + return rows[:TOP_N] +``` + +## 实战数据 (2026-09-19 跑通) + +**Top 8**(全部用同花顺 TTM 数据源): + +| 代码 | 名称 | TTM 股息率 | 连续(年) | 数据源 | +|---|---|---|---|---| +| 603165 | 荣晟环保 | 13.45% | 10 | ths | +| 000915 | 华特达因 | 8.6% | 14 | ths | +| 603551 | 奥普科技 | 7.81% | 7 | ths | +| 300406 | 九强生物 | 7.08% | 12 | ths | +| 002867 | 周大生 | 6.96% | 10 | ths | +| 601919 | 中远海控 | 6.9% | 6 | ths | +| 600015 | 华夏银行 | 6.06% | 24 | ths | +| 600750 | 华润江中 | 5.88% | 26 | ths | + +**耗时**: 25.3 秒(东方财富 14.5s + 同花顺并行 7.7s + 长桥 3s) + +## 关键踩坑 + +| 坑 | 解决 | +|---|---| +| **东方财富 `现金分红-股息率` 是小数(0.0544)不是字符串** | `* 100` 转 % | +| **同花顺 `税前分红率` 是字符串("5.44%")** | `.str.replace('%', '')` + `float()` | +| **akshare 国内接口被全局 HTTP_PROXY 拦截** | 跑脚本前 `unset` 4 个 proxy env | +| **北京银行 5.44% vs 同花顺 7.36%** | **财年滚动**算法 + 含预案 = **正解 7.44%** | +| **同花顺 ths detail 没批量接口** | ThreadPoolExecutor 并行(8 线程够稳,16 触发风控) | +| **拉 5675 只价格超 10 分钟** | 粗筛 top 100 后只拉 100 只(长桥 batch 内部 15/批) | + +## Cron 集成(待 user 决策) + +| 项 | 现状 | +|---|---| +| **脚本路径** | `/tmp/cn_dividend_top8_v5.py`(测试版) | +| **正式版** | 待移到 `~/.hermes/scripts/cn_dividend_top8.py` | +| **cron 频率** | 每周一(用户偏好一周跑一次) | +| **缓存路径** | `~/.hermes/trading/cn_top8.json` | +| **缓存格式** | `[{"code": "601169", "name": "...", "yield_pct": 7.36, "continuous": 19, ...}]` | +| **下游消费** | `cn-dividend-buy-timing` cron 改成读 JSON,不再用 `HIGH_DIVIDEND_POOL` 硬编码 | +| **行业分散** | **不做**(akshare 行业接口代理不稳,且用户未强求) | + +## 同花顺 ths 接口 vs akshare ths 接口对比 + +| 项 | akshare `stock_fhps_detail_ths` | 同花顺 ths 官方 API | +|---|---|---| +| 可用 | ✅ akshare 封装 | ❌ 无官方 Linux 客户端 | +| 速度 | 0.5-0.9 秒/只 | N/A | +| 字段 | 报告期 / 实施公告日 / 税前分红率 / 方案进度 | 同上 | +| 用途 | 数据验证 / 批量校验 | N/A | + +**结论**:akshare ths 接口**字段口径与同花顺 APP 完全一致**——直接用这个。 + +## 反模式(用户骂过的) + +- ❌ 自己写 TTM 算法 → "你一个AI写算法,还能比得过专家吗?" +- ❌ 反复 retry 同花顺接口 / akshare 接口 → "你是不是 API 没调通?" +- ❌ 用 `summary['年均股息'] / 当前价` 当股息率 → 错 27 倍(漏 `/ 10`) +- ❌ 用 `派息 / 当前价` 当股息率 → 错 10 倍(漏 `/ 10`) +- ❌ 5 次算法调试 vs 1 行接口验证 → 用户骂 "我不专业,算出来我也不知道对不对,我也是去看同花顺的" + +## 关联 skill + +- `~/.hermes/skills/prefer-data-over-computation/SKILL.md` —— 算法 vs 源数据原则 +- `~/.hermes/skills/long-toolchain-pacing/references/akshare-a-share-dividend.md` —— akshare 接口细节 + 单位陷阱 +- `~/.hermes/skills/trading/dividend-scanner/references/cn-top8-auto-screen.md` —— v4 算法(已废,但记录"为什么不自己算") \ No newline at end of file diff --git a/dividend-scanner/references/us-dividend-frequency-tier.md b/dividend-scanner/references/us-dividend-frequency-tier.md new file mode 100644 index 0000000..cf1d639 --- /dev/null +++ b/dividend-scanner/references/us-dividend-frequency-tier.md @@ -0,0 +1,132 @@ +# dividend_alert.py 美股频率反推 + 周派票数据缺口 (2026-08-26) + +## 📋 背景 + +`dividend_alert.py` 推送"明天除权的美股"列表时,只显示「每股派/annual/连续估年」, 没法区分: +- 季度派息的稳定票 (EXPE/SOLS) vs +- BDC/REIT 的高频派息陷阱 (PSEC月派, SATA周派) + +用户原话 "PSEC 显示连续 12 年, SATA 显示连续 249 年, 显然公式 bug"。 + +## ✅ 加的修复 (2026-08-26) + +`dividend_alert.py` 加了「频率(估)」列, 用 `round(ann/rate)` 反推年度派息次数 → 档位标签: + +```python +freq_n = round(ann / d) # d = 单次每股派, ann = 年化 +if freq_n >= 50: freq_str = '🔄周W' +elif freq_n >= 25: freq_str = '🔄双周BW' +elif freq_n >= 11: freq_str = '🔄月M' +elif freq_n >= 5: freq_str = '🔄双月BM' +elif freq_n >= 3: freq_str = '📅季度Q' +elif freq_n == 2: freq_str = '📅半年S' +elif freq_n == 1: freq_str = '📅年A' +else: freq_str = '❓' +``` + +| 档位 | 年度次数 | 真实场景 | +|---|---|---| +| 🔄周W | ≈52 | BDC 周派 (SATA, EPRF) — 高息陷阱常见 | +| 🔄双周BW | ≈26 | BDC 双周派 | +| 🔄月M | ≈12 | BDC/REIT 月派 (PSEC, AGNC) — 高息陷阱 | +| 🔄双月BM | ≈6 | 不常见 | +| 📅季度Q | 3-4 | 标准季度派 (EXPE/SOLS/JNJ) | +| 📅半年S | 2 | 半年派 | +| 📅年A | 1 | 年度派 | + +## ⚠️ 周派票数据缺口 (已部分解决 — 2026-08-26) + +Nasdaq 除权日历 API (`api.nasdaq.com/api/calendar/dividends?date=...`) **不收录周派 ETF/BDC**: + +**实测** (2026-08-25): +- QQQY / RDTE 每周四都派息 (yfinance 历史可查) +- 但 Nasdaq API 未来 7 天所有除权日都查不到 QQQY/RDTE +- 周派 ETF 普遍被漏: QQQY/RDTE/EPRF/EFC/STWD 等 + +**根因**: Nasdaq API 的 `indicated_Annual_Dividend` 对周派 ETF 返回 0, 脚本过滤掉 ann=0 的票, 但**根本拉不到除权日信息**。 + +## ✅ 解决方案 (2026-08-26 上线): LongPort MCP `dividend` 工具 + +**关键发现**: 长桥 MCP 的 `mcp_longport_dividend` 工具**有 QQQY/RDTE/EPRF 的完整历史派息**。本会话验证: + +```python +resp = mcp_call('tools/call', { + 'name':'dividend','arguments':{'symbol':'QQQY.US'} +}, ...) +# → list 含 100 条记录, 每条 record_date 是周四 (2026.08.20, 2026.08.13, 2026.08.06...) +# desc 字段: "每股派发 0.1309 USD" (编码乱但能 parse 数字) +``` + +**新工具: `~/.hermes/scripts/dividend_highfreq.py`** + +设计: 全 US 市场动态扫描, 找出高频派息票 (W/BW/M/BM 档)。 + +**数据流**: +1. `security_list` 翻页 (US market, page=1..N, 每页 50 条) → 候选池 +2. `quote` 批量 (一次最多 500) → 当前价格 +3. `dividend` 逐只 (1-4 秒/只) → 历史派息日期 + 金额 +4. 算频率档位 (period_days_avg: ≤5=W, ≤11=BW, ≤18=M, ≤35=BM, ≤70=Q) +5. 筛 yield ≥ 4% 入 SQLite `highfreq_pool` +6. 按 freq + yield + 稳定度 (std/mean cv) 排序推送 + +**cron 频率**: 每日一次 (建议 21:00 北京, 比长桥主推送早 30 分钟), Top 200 票 ~8 分钟跑完。 + +**推送格式** (横表): +``` +🔄 美股高频派息雷达 (YYYY-MM-DD 北京) 数据源: 长桥 MCP +| 票 | 频率 | 连续(期) | 稳定度 | 平均派$ | 现价 | 年化股息率 | +|:---|:---|:---|:---|:---|:---|:---| +| LEND.US | 🔄双月BM | 8 | ⭐⭐⭐ cv=0.05 | $0.193 | $20.50 | **9.41%** | +| VAIE.US | 🔄双周BW | 12 | ⭐⭐⭐ cv=0.04 | $0.077 | $4.74 | **16.27%** | +``` + +⚠️ 高频派息 (W/BW/M/BM) 多为 BDC/REIT, 高息常不可持续, **文化娱乐参考**。 + +## ⚠️ 长桥 `screener_search` 工具的隐性坑 + +实测 (2026-08-26): `screener_search` 的 `dividend` 和 `dpseps` indicator 字段**返 None** (即使请求了), 只返 `prevclose/marketcap/pettm/pbmrq/salesgrowthyoy` 这些。 + +**所以**: +- ❌ 不要试图用 `screener_search filter=[dividend≥X]` 筛高息股 — 返的 dividend 字段全 None +- ✅ 改用 `security_list` 拿全市场 → `quote` 拿现价 → `dividend` 拿历史 → 算 yield +- ✅ `security_list` 支持 `page=N` 翻页 (每页 50 条), `total=16008` (US) + +## 🧠 数据源优先级 (v2 决策) + +| 方案 | 数据源 | 优/缺 | 状态 | +|---|---|---|---| +| A | yfinance 单只拉 | 准 / 慢 | ❌ 太慢 | +| B | yfinance bulk download | 一次 / 数据大,漏票 | ❌ | +| C | ETF.com API | 准 / 付费 | ❌ | +| D | stockanalysis.com HTML scrape | 免费 / 反爬 | ❌ | +| **E** | **LongPort MCP dividend 工具** | **准 / 已知完整覆盖 QQQY/RDTE 等** | ✅ **v2 实装** | +| F | Nasdaq 除权日历 (旧) | 不收周派 | ❌ 已被覆盖 | + +**用户决策 (2026-08-26)**: 走 E。`dividend_highfreq.py` v1.0 已实装, cron 接入待办。 + +## 🛠️ 加列后推送对比 + +**前**: +``` +| PSEC | 💰$0.04 | $0.42 | 🔥12 | 📊$2.35 | **17.87%** | 8/27/2026 | +| SATA | 💰$0.05 | $12.85 | 🔥249 | 📊$100.00 | **12.85%** | 8/27/2026 | +``` + +**后** (frequency column added): +``` +| PSEC | 💰$0.04 | $0.42 | 🔄月M | 🔥12 | 📊$2.35 | **17.87%** | 8/27/2026 | +| SATA | 💰$0.05 | $12.85 | 🔄周W | 🔥249 | 📊$100.00 | **12.85%** | 8/27/2026 | +``` + +新增列**一目了然看出 BDC/REIT 高频派息陷阱**。 + +## 📌 实现位置 + +`~/.hermes/scripts/dividend_alert.py` line ~226 (us_h 表格渲染分支), 改动注释 `2026-08-26 加频率(估)列`, 在 `SKILL.md` 末尾加一行 disclaimer。 + +## 验证 + +```bash +python3 ~/.hermes/scripts/dividend_alert.py --market us +# 验证: 输出表格有"频率(估)"列, 数字标的是 W/M/Q 等档位 +``` \ No newline at end of file