dividend skills: A股高息股自动筛文档 (同花顺口径/东方财富批量/行业分散)
This commit is contained in:
+102
-14
@@ -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写算法还能比得过专家吗").
|
||||
|
||||
@@ -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%。
|
||||
@@ -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 算法(已废,但记录"为什么不自己算")
|
||||
@@ -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 等档位
|
||||
```
|
||||
Reference in New Issue
Block a user