# 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%。