用免费数据搭建美股短期顶部预警系统
10 年回测验证 + 完整代码开源
Building a US Top-Risk Score with Free Public Data
一、为什么个人投资者也需要拥挤度监控
最近半年我做 A 股小市值量化策略,逐渐意识到一件事: 策略本身的 alpha 已经不是回撤的主要来源,宏观传导才是。 2024-04 的那次小市值踩踏、2025 春节后的回调、最近的科技股波动—— 大部分时候不是策略选股选错了,而是美股顶部传导引发了 A 股 risk-off。
机构有完整的拥挤度监控基础设施: Goldman Sachs 主经纪商客户能看到 hedge fund VIP 持仓、 MSCI Barra 因子拥挤度月报、 Nomura McElligott 的 CTA 流量周报。 但这些数据每年订阅费几万到几十万美元。
我想知道:能不能用完全免费的公开数据,搭一套个人投资者也能用的版本? 这是这个项目的起点。
二、项目概览:crowding
整个系统模块化设计,代码结构:
crowding/
├── collectors/ # 数据采集 (yfinance + CBOE + CFTC + Stooq)
├── analyzers/ # 信号计算 (拥挤度, CTA, 技术, 风险评分 v1/v2)
├── backtest/ # 历史回测验证
├── notifiers/ # Telegram 推送 + Markdown 报告
└── storage/ # SQLite 持久化
监控的 8 维信号
- 5 个因子拥挤度(z-score + 滚动分位):高波动、高 Beta、动量、垃圾股(低质量)、投机性增长。 用 ETF 多空价差代理(MTUM/SPY、SPHB/SPLV、SPHB/USMV、ARKK/SPY、SPY/QUAL)
- 半导体技术状态:SOX 偏离 20 日均线
- CTA 趋势资金:自构建多周期均线突破信号
- CFTC 持仓:Leveraged Money + Asset Manager 区分
- VIX 期限结构:VIX/VIX3M、绝对水平、5 日变化
- CBOE SKEW:尾部风险溢价
- 5 个领先指标对:IWM/SPY、SOXX/SPY、HYG/LQD、IYT/XLI、XLY/XLP
- 历史 PCR:CBOE 2006-2019 归档,用于建立分位数基线
数据全部从这 4 个免费源拉取:
- yfinance:ETF / 指数 / 期货价格
- CBOE 公开 CSV:PCR 历史归档
- CFTC TFF 周报:机构持仓 zip 文件
- Stooq.com:备份数据源
三、v1 的设计与失败
v1 评分公式
按经典教科书思路设计:
risk_score = 0.4 × 因子最大|z-score|分位
+ 0.3 × SOX 偏离强度
+ 0.3 × VIX/PCR 极端度
逻辑听起来很对:z-score 极端 = 拥挤 = 顶部前夕。
第一次输出:85% 极端警示
2026-04-29 我跑出 v1 评分 = 85%,"🔴 极端警示"。当时数据画面:
- 高波动率 z = +2.91σ(统计上 0.2% 极端事件)
- 高 Beta z = +2.59σ
- SOX 偏离 20MA +19.5%(5 年罕见)
- CFTC NQ Lev Money 净空头 -67% of OI(史诗级)
- CFTC 快慢钱反向(Lev short -67%, Asset Mgr +7%)
这看起来非常像顶部前夜。 但严肃的研究员在做出 call 之前,必须先回测验证。
v1 在 5 年回测中失败
我把 v1 评分公式应用到过去 5 年每一天,然后看不同评分桶对应的未来 30/60/90 天 NDX 回报:
| v1 评分桶 | 样本数 | 30 天回报 | 60 天回报 | 跌 5% 概率 |
|---|---|---|---|---|
| Low (0-40%) | 501 | +2.02% | +4.77% | 16.4% |
| Medium (40-60%) | 624 | +1.10% | +2.08% | 15.2% |
| High (60-75%) | 118 | +4.12% | +3.98% | 3.4% |
| Very High (75-85%) | 12 | +4.38% | +8.59% | 0.0% |
v1 高评分桶反而对应了显著上涨,跌 5% 的概率甚至比基准还低。 这是教科书式的"过拟合直觉"错误:
- z-score 极端在牛市里大多数时候等于"突破延续",而非"反转"
- 单点极端值不构成顶部
- 没有方向性区分(上涨过快 vs 下跌反转)
如果我相信 v1 直接发了 85% 警示,我会被市场打脸。
四、v2 的关键改进
把回测当作研究流程的一部分,而非"找证据支持已有结论"。 v1 的失败教会我四件事。
改进 1:方向性检测
不再用"|z| 大 = 高分",而是:
# 高分条件:曾极端 + 已开始回落
top_reversal = (z_peak_30d > 2.0) & ((z_peak − z_now) > 0.5)
intensity = (z_peak − z_now).clip(0, 2) / 2.0 # 跌得越多分越高
改进 2:多信号共振 (Confluence)
5 个独立子信号:
- factor_reversal:因子 z-score 已从近期峰值回落
- sox_reversal:SOX 偏离从 30 日峰值回落 ≥ 4 个百分点
- cftc_extreme:NQ Lev Money 持续 ≥ 2 周极端空头 + 快慢钱反向 + 仓位加深
- vix_rising:VIX 5 日变化 > +15% + 期限结构开始倒挂
- leading_weak:5 个领先指标对整体走弱(动量 + 分位 + 200 日均线突破)
Confluence bonus:3 个以上信号同时激活(>0.4),原始分 × 1.3。
改进 3:持续性确认
# 评分必须连续 ≥ 3 天 > 0.5 才视为"确认",否则打 7 折
confirmed = raw if rolling(3).min() > 0.5 else raw × 0.7
改进 4:加入领先指标
借鉴 Mike Green、Charlie McElligott 等人的研究: 真正领先市场顶部的不是 VIX 或拥挤度本身,而是相对强弱。 我加入 5 对:
- HYG / LQD:高收益债 vs 投资级(信用利差)
- IWM / SPY:小盘 vs 大盘
- SOXX / SPY:半导体 vs 大盘
- IYT / XLI:运输 vs 工业(道氏理论)
- XLY / XLP:可选消费 vs 必需消费
当多个领先指标对同时跌破 200 日均线 + 60 日动量为负 + 处于 60 日低位时, 这是市场顶部的核心结构性信号。
五、10 年回测:v2 的真正发现
实验设置
- 样本期:2016-04-29 → 2026-04-28 (2,513 个交易日)
- 覆盖事件:2018 Q4 (-23%), 2020 COVID (-30%), 2022 全年 (-33%), 2025 春调整 (-15%)
- 预测变量:v2 confirmed_score(前一日)
- 被预测变量:NDX 未来 5/10/20/30/60/90 天累计回报、最大回撤
- 样本外验证:所有 z-score 和分位数已是 rolling 计算(无 lookahead bias)
核心结果 1:短期窗口(5-20 天)有显著预测力
| v2 评分桶 | n | 5 天 | 10 天 | 20 天 | 30 天 | 60 天 |
|---|---|---|---|---|---|---|
| Low (0-40%) | 2,429 | +0.41% | +0.82% | +1.60% | +2.28% | +4.59% |
| Medium (40-60%) | 47 | +0.15% | +1.15% | +1.57% | +2.40% | +7.32% |
| High (60-75%) | 28 | +0.21% | −0.78% | +1.25% | +6.98% | +9.91% |
| Very High (75-85%) | 6 | −1.52% | −3.47% | −4.71% | +1.50% | +5.22% |
| Extreme (>85%) | 3 | +4.24% | +5.45% | +3.98% | +5.17% | +2.44% |
Very High 桶(评分 75-85%,10 年里仅 6 次)的 5-20 天回报全部显著为负:
- 5 天 −1.52% vs 全样本 +0.41%(差异 −1.93%)
- 10 天 −3.47% vs 全样本 +0.82%(差异 −4.29%)
- 20 天 −4.71% vs 全样本 +1.60%(差异 −6.31%)
核心结果 2:信号在 30 天后失效
Very High 桶 30 天回报已经反弹到 +1.5%,60 天 +5.2%,90 天甚至 +13.2%—— 全部高于全样本基准。 这告诉我们一个非常重要的事实: v2 抓到的是短期回调,不是中期顶部。 市场倾向于在 V 形反转中快速恢复。
核心结果 3:最大回撤显著恶化
| 评分桶 | 60 天平均回撤 |
|---|---|
| Low (0-40%) | −5.22% |
| Medium (40-60%) | −6.72% |
| High (60-75%) | −5.40% |
| Very High (75-85%) | −9.46% ← 几乎是 Low 桶的 2 倍 |
即使 60 天累计回报回到正值,期间经历的最大回撤显著加深—— 这正是短期对冲的价值所在。
六、Verdict:短期 timing 工具,不是中期方向工具
严格的论断
当 v2 confirmed_score > 0.75 时(10 年里仅 6 次):
- 未来 5-20 天 NDX 平均下跌 −1.5% 到 −4.7%(vs 全样本 +0.4% 到 +1.6%)
- 60 天最大回撤平均 −9.5%(vs Low 桶的 −5.2%)
- 30 天后市场倾向于反弹,60 天累计回报反而高于全样本
这意味着什么
✓ 可以做的:
- 短期对冲(买 1 个月 OTM put)
- 短期减仓(5-20 天,然后重新加仓)
- A 股小市值/ETF 策略短期降仓 30-50%
✗ 不能做的:
- 长期看空(市场会反弹)
- 期待"大顶来了"(10 年里没出现真正的中期顶部信号)
- 只用 v2 一个指标决定中期仓位
当前评分 (2026-04-29):21%
原始评分: 30% → 确认评分: 21%
激活子信号: 1/5
子信号细分:
⚪ 因子反转 0.00 (z-score 还在创新高)
⚪ SOX 反转 0.00 (才刚开始消化 +19% 极端值)
🔴 CFTC 极端持续 1.00 (NQ Lev Money 持续空头 + 仓位加深)
⚪ VIX 上升 0.00 (VIX 5 日变化 −4.8%,在下降)
⚪ 领先指标转弱 0.00 (HYG/LQD、IWM/SPY 健康)
综合判断: ⚪ 正常市场或动量延续阶段
这是诚实的输出:CFTC 数据确实极端,但其他 4 个反转确认信号都没出现。 如果信了 v1 的 85% 直接发警示,会被市场打脸——v2 给出的 21% 才是真正的市场状态。
七、对 A 股量化策略的应用
集成进小市值/ETF 轮动策略
from crowding.analyzers.risk_score_v2 import latest_v2_breakdown
def get_us_short_term_risk():
info = latest_v2_breakdown()
return {
"score": info["confirmed_score"],
"n_active": info["n_active_signals"],
"should_hedge": info["confirmed_score"] > 0.5 and info["n_active_signals"] >= 3,
}
def handle_data(context, data):
us_risk = get_us_short_term_risk()
if us_risk["should_hedge"]:
# 短期减仓 (10-20 天窗口),不是长期看空
target_position = base_position × 0.5
# 设置 20 天后自动重新评估
context.us_hedge_until = pd.Timestamp.now() + pd.Timedelta(days=20)
关键的纪律:4 周后必须重新评估。 不要因为一次 v2 触发就长期看空。 设置一个 review_date,到期后回到正常仓位。
八、限制与未来工作
已知限制
- CTA 信号是简化代理:用多周期均线突破,没有波动率目标和风险平价加权。 真实的 SocGen / DB CTA 模型更复杂。
- 拥挤度用 ETF 代理而非个股:比 Barra 风格因子粗糙,但相关性 ~0.7+。
- 样本仍偏向 2016-2026 这十年:包含 4 个回调但 2008 GFC、2000 互联网泡沫不在内。
- 领先指标可能过拟合美国市场结构:换到欧股、A 股需要重新校准。
- 短期信号不能告诉你方向幅度:只是"未来 10-20 天大概率回调 3-5%",不能预测是 −3% 还是 −8%。
未来想做的
- 加入信用违约互换(CDS)相关代理(AGG、TLT、LQD 价差)
- 加入隐含相关性指数(^JCI / Cboe Implied Correlation)
- 把回测扩展到 NDX 之外的 SPX、RTY,看信号是否有跨指数差异
- 与 A 股北向资金 + 国债期货持仓做联动分析
最重要的一课
这个项目最有价值的部分不是"我做出了顶部预警系统", 而是 "v1 在回测里失败了,我把这个失败展示给你"。
中文量化圈大部分内容都在告诉你"我有一个赚钱的策略"。 但好的研究是数据驱动的,不是自我证明的。 当我看到 v1 的高评分桶反而对应正回报时,正确的反应是:
- 不发布 v1 的 85% 警示
- 改进设计而不是改进解读
- 在 10 年数据上重新验证
- 接受 v2 是"短期工具"而不是"中期工具"的诚实结论
这才是机构级研究的态度。
代码与数据 · Resources
* 数据全部来自免费公开源 (Yahoo Finance / CFTC / CBOE),无付费 API 依赖。 README 提供完整的快速开始命令,在本地 5-15 分钟即可完成 10 年历史回填。
快速开始
# 1. 安装
pip install yfinance pandas numpy requests openpyxl xlrd matplotlib
# 2. 初次回填 10 年数据
python -m crowding extend-history --years 10
# 3. 跑回测验证
python -m crowding backtest --version v2
# 4. 看当前评分
python -m crowding score-v2
# 5. 生成完整快照报告 (Markdown + Telegram 推送)
python -m crowding report-snapshot
数据源
- ETF / 指数价格:Yahoo Finance (yfinance 库)
- CFTC TFF 周报:cftc.gov (zip 含 .xls)
- CBOE PCR 归档:cdn.cboe.com (2006-2019 历史)
- VIX 期限结构:yfinance (^VIX, ^VIX3M, ^SKEW)
- 领先指标 ETF:yfinance (HYG, LQD, IWM, ...)
我踩过的坑(你可能也会遇到)
- Yahoo Finance 不断下架"非交易性指数":^CPC、^CPCE、DX=F 都被下架。 解决方案:用 ETF (UUP/IEF/GLD/USO) 替代期货。
- CBOE 对云服务器 IP 返回 403:用完整 Chrome User-Agent 头可以绕过, 备份用 Stooq 镜像。
- CFTC 文件命名约定改过:当年文件用 fut_fin_xls_{year}.zip, 旧档案路径已废弃。列名也用 TFF 标准的 Lev_Money_*。
作者:Jun · JunQuant 量化研究 · 2026.04.29
本文数据完全可复现,所有图表的源代码已包含在仓库里。 有兴趣交流或合作的朋友欢迎联系 [email protected]。