4.8 KiB
A 股 Top N 自动筛 (替换硬编码候选池)
背景: cn_dividend_buy_timing.py 原本用 8 只硬编码候选池 (601288/601398/601088/600023/600585/601006/601939/601658)。用户 2026-09-17 要求改为自动从全市场筛 Top N,并按行业分散。
数据源 (优先东方财富, 因为字段已含股息率)
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_numericak.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 (字段是小数, 不是 %)
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 只):
# 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 实测)
pd.to_numeric(s.str.replace('%', ''))处理 None / "--": 用errors='coerce'+.dropna()- 东方财富字段是 0.15 不是 "0.15%" (小数 vs 百分比字符串) — 乘 100 别漏
- 拉 5675 只价格太慢 (>10 分钟) — 粗筛按 "派息次数 × 累计股息" 取 top 200, 只拉这些价格
- 代理 env 全局干扰 akshare 国内 API: 跑脚本前
unset4 个 proxy var - 同花顺 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%。