美股拥挤度实验室 Issue #01

用免费数据搭建美股短期顶部预警系统
10 年回测验证 + 完整代码开源

Building a US Top-Risk Score with Free Public Data

一句话版 · TL;DR
第一版评分(v1)在 5 年牛市样本里没有预测力——这是常见的"事后诸葛亮"错误。 第二版(v2)加入方向性检测、多信号共振、领先指标后, 在 5-20 天窗口显示统计可见的预测力: 当评分进入 75-85% 区间(10 年里仅 6 次),未来 10 天 NDX 平均下跌 -3.5% (vs 全样本 +0.8%),20 天最大回撤显著恶化至 -9.5%。 但 30 天后市场倾向反弹——这是短期 timing 工具,不是中期方向工具。 完整代码已开源。

一、为什么个人投资者也需要拥挤度监控

最近半年我做 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 回报:

Table 01 · v1 5 年回测
v1 评分桶对应的 NDX 前向回报
Source: 自建回测 / 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 个独立子信号:

  1. factor_reversal:因子 z-score 已从近期峰值回落
  2. sox_reversal:SOX 偏离从 30 日峰值回落 ≥ 4 个百分点
  3. cftc_extreme:NQ Lev Money 持续 ≥ 2 周极端空头 + 快慢钱反向 + 仓位加深
  4. vix_rising:VIX 5 日变化 > +15% + 期限结构开始倒挂
  5. 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 天)有显著预测力

Figure 01 · v2 分桶平均前向回报
v2 评分桶 → NDX 未来 5/10/20/30/60 天平均回报
Source: crowding 自建回测
v2 分桶平均前向回报
Table 02 · v2 分桶细节
10 年 v2 评分桶 vs NDX 前向回报
n = 2,513 trading days
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 天后失效

Figure 02 · 信号衰减
v2 信号在 5-20 天有效,30 天后失效
All-sample baseline vs Very High bucket
信号衰减

Very High 桶 30 天回报已经反弹到 +1.5%,60 天 +5.2%,90 天甚至 +13.2%—— 全部高于全样本基准。 这告诉我们一个非常重要的事实: v2 抓到的是短期回调,不是中期顶部。 市场倾向于在 V 形反转中快速恢复。

核心结果 3:最大回撤显著恶化

Table 03 · 60 天最大回撤
v2 评分桶 → 未来 60 天 NDX 最大回撤
评分桶 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,到期后回到正常仓位。

八、限制与未来工作

已知限制

  1. CTA 信号是简化代理:用多周期均线突破,没有波动率目标和风险平价加权。 真实的 SocGen / DB CTA 模型更复杂。
  2. 拥挤度用 ETF 代理而非个股:比 Barra 风格因子粗糙,但相关性 ~0.7+。
  3. 样本仍偏向 2016-2026 这十年:包含 4 个回调但 2008 GFC、2000 互联网泡沫不在内。
  4. 领先指标可能过拟合美国市场结构:换到欧股、A 股需要重新校准。
  5. 短期信号不能告诉你方向幅度:只是"未来 10-20 天大概率回调 3-5%",不能预测是 −3% 还是 −8%。

未来想做的

  • 加入信用违约互换(CDS)相关代理(AGG、TLT、LQD 价差)
  • 加入隐含相关性指数(^JCI / Cboe Implied Correlation)
  • 把回测扩展到 NDX 之外的 SPX、RTY,看信号是否有跨指数差异
  • 与 A 股北向资金 + 国债期货持仓做联动分析

最重要的一课

这个项目最有价值的部分不是"我做出了顶部预警系统", 而是 "v1 在回测里失败了,我把这个失败展示给你"

中文量化圈大部分内容都在告诉你"我有一个赚钱的策略"。 但好的研究是数据驱动的,不是自我证明的。 当我看到 v1 的高评分桶反而对应正回报时,正确的反应是:

  1. 不发布 v1 的 85% 警示
  2. 改进设计而不是改进解读
  3. 在 10 年数据上重新验证
  4. 接受 v2 是"短期工具"而不是"中期工具"的诚实结论

这才是机构级研究的态度。

代码与数据 · Resources

数据采集 collectors/ (yfinance + CFTC + CBOE)
信号计算 analyzers/risk_score_v2.py
回测引擎 backtest/historical.py
运行环境 Python 3.11+ / Ubuntu
数据周期 2016.04 — 2026.04
回测样本 2,513 trading days
数据库 SQLite (~50MB)
部署方式 cron + FastAPI
在 GitHub 上查看完整代码 含 v1/v2 评分 · 10年回测引擎 · MIT 协议 · 欢迎 PR

* 数据全部来自免费公开源 (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, ...)

我踩过的坑(你可能也会遇到)

  1. Yahoo Finance 不断下架"非交易性指数":^CPC、^CPCE、DX=F 都被下架。 解决方案:用 ETF (UUP/IEF/GLD/USO) 替代期货。
  2. CBOE 对云服务器 IP 返回 403:用完整 Chrome User-Agent 头可以绕过, 备份用 Stooq 镜像。
  3. CFTC 文件命名约定改过:当年文件用 fut_fin_xls_{year}.zip, 旧档案路径已废弃。列名也用 TFF 标准的 Lev_Money_*。

作者:Jun · JunQuant 量化研究 · 2026.04.29

本文数据完全可复现,所有图表的源代码已包含在仓库里。 有兴趣交流或合作的朋友欢迎联系 [email protected]