ETF 龙头关注策略:跨市场配置的量化实现
一句话结论 #
个股策略盯的是超跌反弹的择时机会,而 ETF 策略解决的是另一件事——用一篮子真实龙头降低个股地雷风险,实现跨市场、跨风格配置。两者完全独立:独立建池、独立信号、独立推送。
flowchart TD
A[量化系统] --> B[📈 个股策略]
A --> C[📦 ETF策略]
B --> B1[stock_pool.py: 17只]
B --> B2[超跌反弹+趋势择时]
B --> B3[cron 09:30 推送]
C --> C1[etf_pool.py: 9只]
C --> C2[跨市场板块信号]
C --> C3[cron 09:35 推送]
一、为什么专门做 ETF,而不是都买个股 #
把盈利将这句话作为前提:“发现龙头,拥抱龙头,陪伴龙头”。但拥抱龙头有个现实难题——单一个股的龙头地位可能被技术迭代颠覆,也可能因为估值、情绪被错杀。
ETF 用一个结构化解掉这个难题:把行业里公认的龙头打包成一只基金,跟踪指数、分散持仓。这样:
- 享受行业的龙头红利,又规避押注单一公司的风险
- 无需深入研究每一家公司的财报,行业景气度就够
- 流动性好,买卖成本低(场内 ETF 免印花税)
而且 ETF 天然适配你的跨市场配置需求:A股半导体、海外科技、红利防御,一个池子全装下。
二、ETF 池结构(etf_pool.py) #
代码锚点:/home/ubuntu/quant_repo/etf_pool.py
ETFS = {
# 科技/半导体(进攻主线)
"512480": "半导体ETF", # 国内半导体产业链龙头打包
"588000": "科创50ETF", # 科创板50大龙头(中芯/海光/中微)
"515000": "科技ETF",
"159995": "芯片ETF",
# 宽基(防御/均衡)
"510300": "沪深300ETF",
# 海外/美股(全球化配置)
"513100": "纳指ETF", # 纳斯达克100,美股科技龙头
"513500": "标普500ETF", # 标普500,美股大盘
# 医药 / 红利
"512010": "医药ETF",
"510880": "红利ETF", # 上证红利,高股息价值
}
GROUPS = {
"半导体/AI": ["512480", "588000", "515000", "159995"],
"宽基/防御": ["510300"],
"海外/美股": ["513100", "513500"],
"医药/健康": ["512010"],
"红利/价值": ["510880"],
}
设计取舍 #
- 为什么分组:日报按板块展示,一眼看出哪条主线在动,避免 9 只 ETF 堆成一坨分不清。
- 为什么只选代表:每个板块只放最强那只,不搞重复。纳指你只要一个
513100,标普你只要513500,再加别的同类就是信息冗余。 - 池子独立文件:和股票池
stock_pool.py彻底分开,增删互不影响,单独维护。
三、数据层的坑:ETF 代码识别 + 反爬 #
这是整个策略最容易翻车的地方。ETF 代码以 5(沪)和 1(深)开头,而普通股票是 6/0/3。如果数据源不识别这些前缀,ETF 会全部抓不到数据。
修复:识别交易所前缀 #
代码锚点:/home/ubuntu/quant_repo/data/fetcher.py
# akshare 需要带交易所前缀
if symbol.startswith(("6", "5", "688", "9")): # 沪市股票 + ETF + 科创板
ak_symbol = f"sh{symbol}"
elif symbol.startswith(("0", "3", "1")): # 深市股票 + ETF
ak_symbol = f"sz{symbol}"
反爬:akshare 拿不到,走 baostock 兜底 #
实测发现:akshare 的东方财富接口对 ETF 有反爬,会返回 RemoteDisconnected 断连或解析失败。但 baostock 完全支持 ETF,数据完整。
依赖现有的多源自动 fallback机制,上层接口不变,一个源挂了自动切下一个:
[akshare] 获取 512480 失败 → 自动切 [baostock] 成功 (41行)
[akshare] 获取 513100 失败 → 自动切 [baostock] 成功 (42行)
这验证了那句架构原则:多源 fallback 比单源加缓存可靠。真正的兜底是"还有别的源能用",而不是"重试同一个坏源"。
四、信号计算(etf_monitor.py) #
与个股共用 RSI 超跌逻辑,但独立实现,参数可单独调:
def calc_etf_signal(code, fetcher, today):
df = fetcher.get_stock_daily(code, start, end)
close = df["close"].values
chg5d = (close[-5] - close[-1]) / close[-5] # 5日跌幅
rsi = compute_rsi(close[-14:]) # 14日RSI
score = 0
if chg5d > 0.03: score += 1; reasons.append(f"5日跌{chg5d:.1%}")
if rsi < 30: score += 2; reasons.append(f"RSI({rsi:.0f})深度超卖")
elif rsi < 38: score += 1; reasons.append(f"RSI({rsi:.0f})偏低")
if chg1d > 0 and rsi < 40: score += 1; reasons.append("企稳")
return {"signal": "buy" if score >= 3 else "hold", ...}
设计取舍 #
- ETF 信号阈值比个股宽松(score≥3 vs 个股≥4):因为 ETF 波动小、不需要太苛刻的深度超跌,回调稍深就是配置机会。
- 宽基/防御 ETF 不追高:日报末尾会提示"宽基做配置用,不追高",红利这类慢牛标的要控制预期。
五、与个股独立配置 + 独立推送 #
独立 cron #
个股日报和 ETF 日报是两个完全独立的任务:
股票日报 scheduled_daily.py -> cron 09:30
ETF日报 etf_monitor.py -> cron 09:35(工作日)
为什么要 09:35 而不是挤在 09:30?因为微信 iLink 有 30 秒冷却,两条消息同时发会互相触发限流、导致一条被吞。错开 5 分钟,既独立又稳定。
增删方式(你说了算) #
和股票池一样,ETF 池也每天在日报里列出,你拍板增减:
- 「加 XX ETF」→ 改
etf_pool.py的ETFS+GROUPS - 「删 XX ETF」→ 移除对应行
- 下次 09:35 日报自动反映
六、这套池子现在长什么样 #
2026-08-28 实跑输出:
▫️ 半导体/AI
◽ 半导体ETF(512480) 现价1.063 日+4.5% RSI47 无信号
◽ 科创50ETF(588000) 现价1.788 日+3.9% RSI37 偏低|企稳
▫️ 宽基/防御
◽ 沪深300ETF(510300) 现价4.691 日+0.8% RSI54 无信号
▫️ 海外/美股
◽ 纳指ETF(513100) 现价2.229 日+1.2% RSI37 偏低|企稳
◽ 标普500ETF(513500) 现价2.708 日+0.0% RSI47 无信号
▫️ 医药/健康
◽ 医药ETF(512010) 现价0.386 日+0.5% RSI38 企稳
▫️ 红利/价值
◽ 红利ETF(510880) 现价3.454 日+0.4% RSI55 无信号
9 只 ETF,覆盖进攻(半导体/AI)、平衡(宽基/海外)、防守(医药/红利)三条线,跨 A股+美股两个市场。
验证命令 #
# 更新池文件
cd /home/ubuntu/quant_repo && source .venv/bin/activate
python etf_pool.py
# 手动跑 ETF 日报(验证数据+信号)
python etf_monitor.py
# 单只 ETF 数据是否可拉
python -c "from data.fetcher import DataFetcher; f=DataFetcher(); df=f.get_stock_daily('513100','2026-07-01','2026-08-28'); print(df.tail(2))"
下一步 #
这套 ETF 池目前是监控模式——盯信号、给配置建议,但不自动交易。后续可以:
- 给 ETF 也建买入/止盈/止损的执行器,和个股一样跑模拟盘
- 增加 ETF 与个股的相关性分析,做真正的组合层面风控(避免看似分散实则同涨同跌)
- 评估是否纳入黄金/债券 ETF做避险压舱石
由 AI 基金经理自动管理,持续进化中。