别把鸡蛋放进同一个"假装不同"的篮子——分散的本质是收益来源不同,而不是代码不同
一个纯离线的"相关性实验室" scripts/correlation_lab.py:它读入第 2 课落地的茅台真实日线,用 numpy 亲手"造"出两只合成股票——一只与茅台的日收益高相关(目标 0.9),一只低相关(目标 0.1),随机种子固定,任何人重跑都得到一模一样的数字。然后算三样东西:三者的相关系数矩阵、"等权组合 vs 单票"的年化波动率对比、以及滚动相关系数的漂移幅度。
上一课你学了"每笔该押多少"(仓位管理),这节课往上一层:你手里的几个标的、几个策略加在一起,到底算不算一个组合。答案经常是否定的——而你能亲手用数字证明它。
新手对"分散投资"的理解通常是"多买几只"或"多跑几个策略"。这个直觉错在把形式当成了本质:
量化这个"同步程度"的工具就是相关系数:+1 是完全同步,0 是互不相关,-1 是完全反向。这节课的实验就是让你亲眼看到:相关系数 0.9 的"分散",几乎等于没分散。
脚本没有用任何真实股票的第二份数据(东财接口对本机的风控还没解,能离线就离线),而是用一个标准的统计学构造。核心三行(这是展示片段,不用复制到终端):
z = (r_base - r_base.mean()) / r_base.std(ddof=0) # 茅台日收益标准化
noise = rng.standard_normal(len(r_base)) # 独立的随机噪声
syn = rho * z + np.sqrt(1 - rho ** 2) * noise # 按权重混合
直觉很简单:rho 是"茅台成分"的权重,剩下的 1−rho² 开根号是"独立噪音"的权重。rho=0.9 时,合成股九成的波动跟着茅台走;rho=0.1 时,只有一成的波动跟茅台有关。数学上可以证明这样造出来的序列与茅台的理论相关系数恰好是 rho(实测会因为噪声的随机性略有偏差,这正是后面第五节的教学素材)。
注意 rng = np.random.default_rng(SEED) 这一行:随机种子固定为 42,你、我、任何人重跑这个脚本,得到的每一个数字都完全相同。可复现性是量化研究的基本礼仪——让别人(和三个月后的你)能逐位验证你的结论。
全程离线,数据用第 2 课落地的 data/600519_qfq_2024.csv:
cd "$HOME/AI Project/Quant Learning"
source .venv/bin/activate
python scripts/correlation_lab.py
你会看到(我已在你的环境实测通过):
数据:600519_qfq_2024.csv,241 个日收益样本(2024-01-03 ~ 2024-12-31)
========== 日收益相关系数矩阵 ==========
茅台 合成A_高相关 合成B_低相关
茅台 1.00 0.92 0.02
合成A_高相关 0.92 1.00 -0.00
合成B_低相关 0.02 -0.00 1.00
===== 等权组合 vs 单票(年化波动率)=====
茅台单票 茅台+A(高相关) 茅台+B(低相关)
--------------------------------------------------------
年化波动率 27.55% 26.85% 20.08%
--------------------------------------------------------
高相关组合只把波动率降了 2.5%——假装分散;
低相关组合把波动率降了 27.1%——这才叫分散。
===== 茅台 vs 合成B 的 20 日滚动相关系数 =====
目标值 0.1,但实际观测在 -0.47 ~ 0.56 之间漂移
相关系数是历史统计量,不是物理定律——危机时资产相关性会集体冲向 1。
先看相关系数矩阵。造数时目标值是 0.9 和 0.1,实测 0.92 和 0.02——噪声的随机性让实现值偏离了目标值,这是正常的抽样误差,恰恰说明相关系数本身是"测出来的统计量",不是造数时钉死的参数。
再看波动率这张表,它是本课的核心:
| 组合 | 年化波动率 | 相对单票 | 解读 |
|---|---|---|---|
| 茅台单票 | 27.55% | — | 基准 |
| 茅台 + 合成A(相关 0.92) | 26.85% | 只降了 2.5% | 两份钱买了一份风险,假装分散 |
| 茅台 + 合成B(相关 0.02) | 20.08% | 降了 27.1% | 真分散:一半的波动被相互抵消 |
背后的数学只有一行:两个波动率相同(σ)的资产等权组合,组合波动率 = σ × √((1+ρ)/2)。代入看看它和实测贴得多紧:ρ=0.92 时理论值是 0.98σ(实测 26.85/27.55 ≈ 0.97),ρ=0.02 时理论值是 0.71σ(实测 20.08/27.55 ≈ 0.73)。这个公式值得你记住:ρ 接近 1 时,√((1+ρ)/2) 也接近 1——分散效果对高相关资产几乎为零。波动率被对冲掉的那部分,来自两只股票"你跌我涨、各走各的"的相互抵消;相关系数 0.92 意味着这种抵消几乎不存在。
第 7 课你扫了 16 组双均线参数。现在用这节课的眼光再看它们:5/20、8/30、10/60……这些策略跑在同一只股票、同一份行情上,信号大同小异,收益序列的相关系数高得吓人。把它们"组合"起来,就像本课的茅台+合成A——三个策略,一个篮子。真正的策略组合分散,要分散的是收益来源:趋势、反转、情绪、不同行业、不同资产类别,而不是同一策略的不同参数。参数不同只是"代码不同",收益来源不同才是"篮子不同"。
脚本的最后一段输出是本课最重要的警示。合成B 是奔着"与茅台相关 0.1"造的,但把窗口缩到 20 个交易日去看滚动相关系数,实测在 -0.47 ~ +0.56 之间来回漂移——哪怕在一个"构造上就低相关"的序列上,任何一小段历史里的相关系数都可以偏到十万八千里。
这意味着两件事:
对 T+1 的 A 股,这个警示还要再加一层:危机中你连"当天认错离场"都做不到——今天买入的仓位明天才能卖。相关性失效 + T+1,意味着组合的极端风险比你看着回测净值曲线想象的要大。
你的 Agent 辅助决策系统到现在已经覆盖了:单票的分析(情绪、异动)、单策略的研发(回测、调参)、单仓位的风控(仓位管理)。这节课补上了组合视角:当 Agent 将来同时给你推荐"加仓白酒龙头"和"加仓食品饮料 ETF"时,你要能问出那个关键问题——这两条建议的收益来源是同一个吗?让 Agent 顺手算一个相关矩阵(就是本课的 .corr() 一行),是它能做的;判断"0.92 意味着这两条建议其实是一条",是你的工作。
凭记忆作答——答错比答对更能加固记忆。
把脚本里的TARGET_HIGH从 0.9 改成 0.7、把SEED改成别的数,各重跑一次。回答两个问题:ρ=0.7 的组合波动率离"没分散"还有多远?换种子后哪些数字变了、哪些结论没变?想清楚第二个问题,你就分清了"统计噪声"和"结构性结论"。
第 17 课 · 整合项目:每日盘后工作流——把前 16 课的组件(数据、情绪、监控、回测、仓位、组合)串成一条"收盘后每晚跑一次"的完整流水线,你的 Agent 辅助决策系统就此成型。
有任何不清楚的地方(比如相关矩阵怎么读、波动率公式、滚动窗口的含义),直接问我——我是你的老师。