A股主题ETF 波动率排名
适用场景
用户要求对某一主题的全部 A 股场内 ETF 按波动率排名,或做风险横向对比。
触发词:按波动率排名、波动率对比、ETF 风险排名、哪个红利 ETF 最稳。
核心链路
- 确定 ETF 池 → 2. 抓 K 线 → 3. 算多窗口波动率 → 4. 流动性过滤 → 5. 双源校验 → 6. 出 HTML → 7. 渲染自检
1. 确定 ETF 池
用 agentic_search 委派一句话:"列出场内A股<主题>主题ETF"(不要列字段/维度/表格要求)。
返回的清单通常已含 跨境产品污染(红利主题里约 1/3 是港股通/恒生红利)。
硬性过滤:剔除名称含 港股 / 恒生 / 香港 的代码,否则会把跨境 QDII 混进 A 股排名 —— 这是最高频的硬错误。
2. 抓 K 线(关键避坑)
| 源 | 接口 | 复权 | 成交量单位 | 备注 |
|---|---|---|---|---|
| 腾讯 | web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=sh510880,day,,,760,qfq |
前复权 | 手 | 数据最新(含当日盘中) |
| 新浪 | money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol=sh510880&scale=240&ma=no&datalen=800 |
不复权 | 股 | 需先 re.sub(r'(\w+):', r'"\1":', t) 转 JSON |
| 东财 | push2his.eastmoney.com/api/qt/stock/kline/get?... |
— | — | 常直接拒连,不要依赖 |
避坑(实测):
- 腾讯 web.ifzq.gtimg.cn 在连续大量请求后会返回 HTTP 501(疑似限流);新浪此时仍可用 → 新浪做主力源、腾讯做交叉校验更稳。
- 不要开后台任务并发抓取,会加剧限流;串行 + time.sleep(0.1~0.2)。
- 腾讯 K 线数组格式:[date, open, close, high, low, volume](第 2 位是 close,不是 volume)。
- 成交量→成交额:腾讯 额 = close × volume × 100(手转股);新浪 额 = close × volume。
- 解析出的 ETF 全名在 data[code].qt[code][1],比搜索给的简称准。
3. 波动率计算
ANN = 243 # A股年均交易日(不要用 252)
def av(cl, w):
if len(cl) < w + 1: return None
s = cl[-(w+1):]
r = [s[i]/s[i-1] - 1 for i in range(1, len(s))]
return statistics.pstdev(r) * math.sqrt(ANN) * 100 # 总体标准差
窗口取 20 / 60 / 120 / 250 日 + 近 3 年。主指标用 60 日(20 日噪声大、250 日钝化)。
4. 流动性过滤(必做)
波动率排名不做流动性过滤就是错的。实测教训:某主题池中波动率的最低值和最高值都落在日均成交 < 1000 万元的迷你产品上 ——
低波来自报价黏滞(长期横盘零成交),高波来自单笔大单跳价。两者都不能与主流品种横向比较。
- 流动性 = 近 60 日
成交量 × 收盘价日均值。 - 分档阈值:≥ 5000 万元为"主流可投",< 1000 万元必须打标警示。
- 输出时同时给 全样本榜 和 主流榜,并画 波动率 vs 流动性对数散点图暴露这个偏误。
5. 双源交叉校验
同一批代码分别用腾讯(前复权)和新浪(不复权)算 60 日波动率,比对差异。
实测:平均差异 0.15pp、最大 0.51pp —— 这个量级说明 ETF 分红除息对 60 日窗口的抬升可忽略,排序结论稳健。
报告中要把这个校验结果写出来,是可信度背书。
6. HTML 研报结构
浅底深字研报风,首屏结论先行。图/表可切换。固定 5 个图:
- 排名横向条形图(主流榜 / 全样本榜切换)—— 按指数族配色
- 波动率 vs 流动性散点(y 轴 log)—— 暴露迷你产品偏误
- 指数族分层图 —— 用两个 stack bar 模拟区间条(透明柱 + 灰柱),不要用 custom series 手写 renderItem(易错且收益低)
- 主流品种多窗口分组柱(20/60/120/250)—— 验证排名不是短窗口噪声
- 全样本明细表
关键技术选择:数据用 <script id="PAYLOAD" type="application/json"> 注入,JS 里 JSON.parse 读取 —— 避免手写大数组导致的引号/括号失配。
散点图数据必须是 {value:[x,y], ...},只给 x/y 字段 ECharts 不会绘制。
7. 渲染自检(交付前必做,两级)
# 阶段1:语法
node --check _check.js
# 阶段2:真实渲染(jsdom 捕获 option + echarts SSR 渲染)
cd /Users/bytedance/.workbuddy/binaries/node/workspace
npm install jsdom echarts --no-audit --no-fund
node render_check.js
render_check.js 思路:
- jsdom runScripts:'outside-only' + mock window.echarts 记录所有 setOption(option) → 验证脚本能跑通、DOM 查找无 null
- 再把每个 option 交给 echarts.init(null,null,{renderer:'svg',ssr:true}) 真实渲染 → 检查 SVG 字节数和 <path|rect|circle|text> 元素数,判断是否空白
这一步比 node --check 强得多:能抓到「语法正确但图表空白」的配置错误(如散点缺 value、custom series 坐标错)。
输出模板(对用户说的话)
按指数族分层的结论 > 单个产品排名。核心洞察通常是:
"波动率排序由指数编制规则(有没有低波因子)决定,而不是由主题名称决定。"
再补一句流动性警示,最后附免责声明。