第 7 章 · 03 BL 实战 本节摘要:理论讲完,实战登场。本节把前两节拼成一条完整流水线:取市值 → 推先验 → 写观点 → 配置信度 → 融合得后验 → 喂给 EfficientFrontier 出权重,完全对应官方 Cookbook 4。同时把 BL 后验 vs 纯历史均值两种收益输入做横向对比,直观看到「BL 让权重更分散、更稳定」。读完本节,你能端到端跑完一个 BL 优化流程,并说清它相对纯均值-方差的优势。 内容来源:cookbook 、文档 、源码 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 端到端跑完 BL 流水线(先验→观点→后验→优化)。 用 快捷构造 BlackLittermanModel。
本节摘要:理论讲完,实战登场。本节把前两节拼成一条完整流水线:取市值 → 推先验 → 写观点 → 配置信度 → 融合得后验 → 喂给 EfficientFrontier 出权重,完全对应官方 Cookbook 4。同时把 BL 后验 vs 纯历史均值两种收益输入做横向对比,直观看到「BL 让权重更分散、更稳定」。读完本节,你能端到端跑完一个 BL 优化流程,并说清它相对纯均值-方差的优势。
内容来源:cookbook
4-Black-Litterman-Allocation.ipynb、文档docs/BlackLitterman.rst、源码pypfopt/black_litterman.py,汉化并套用体系化模板。
阅读完本节,你应当能够:
pi="market" 快捷构造 BlackLittermanModel。cookbook 4 的开端——拉数据,算协方差、δ、先验:
import numpy as np import pandas as pd import matplotlib.pyplot as plt import yfinance as yf from pypfopt import black_litterman, risk_models from pypfopt import BlackLittermanModel, plotting tickers = ["MSFT", "AMZN", "NAT", "BAC", "DPZ", "DIS", "KO", "MCD", "COST", "SBUX"] ohlc = yf.download(tickers, period="max") prices = ohlc["Close"] market_prices = yf.download("SPY", period="max")["Close"] mcaps = {} for t in tickers: mcaps[t] = yf.Ticker(t).info["marketCap"] # 协方差(用 Ledoit-Wolf 收缩) S = risk_models.CovarianceShrinkage(prices).ledoit_wolf() # 风险厌恶 delta = black_litterman.market_implied_risk_aversion(market_prices) # 市场隐含先验 market_prior = black_litterman.market_implied_prior_returns(mcaps, delta, S) market_prior.plot.barh(figsize=(10, 5))
market_prior 是「市场当前定价隐含的收益预期」,会画出一张条形图——市值大的资产隐含收益往往更温和,小盘股 NAT 这种隐含收益最高。
cookbook 4 用的绝对观点:
viewdict = { "AMZN": 0.10, "BAC": 0.30, "COST": 0.05, "DIS": 0.05, "DPZ": 0.20, "KO": -0.05, # 我看跌可口可乐 5% "MCD": 0.15, "MSFT": 0.10, "NAT": 0.50, # 看涨 NAT 50%,但低置信度 "SBUX": 0.10, }
💡 观点质量 > 数量:BL 的稳健性来自「只表达有把握的观点」。NAT 看涨 50% 看似激进,但配低置信度,后验里会被显著稀释。
cookbook 4 提供了两种 omega 构造法。第一种 Idzorek 百分比:
confidences = [ 0.6, 0.4, 0.2, 0.5, 0.7, # 对 DPZ 自信 0.7, # 对 KO 看跌自信 0.7, 0.5, 0.1, 0.4, ] bl = BlackLittermanModel( S, pi=market_prior, absolute_views=viewdict, omega="idzorek", view_confidences=confidences )
第二种用 68% 置信区间反推:
intervals = [ (0, 0.25), (0.1, 0.4), (-0.1, 0.15), (-0.05, 0.1), (0.15, 0.25), (-0.1, 0), (0.1, 0.2), (0.08, 0.12), (0.1, 0.9), (0, 0.3), ] variances = [((ub - lb) / 2) ** 2 for lb, ub in intervals] omega = np.diag(variances)
或最省事——直接 pi="market" 让库内部算先验:
bl = BlackLittermanModel( S, pi="market", market_caps=mcaps, risk_aversion=delta, absolute_views=viewdict, omega=omega )
pi="market" 是个语法糖——把第 7-01 节讲的 market_implied_prior_returns 内联进构造函数,等价于先 market_prior = ... 再传 pi=market_prior,但少一行代码。
ret_bl = bl.bl_returns() S_bl = bl.bl_cov() # 可视化对比:Prior / Posterior / Views rets_df = pd.DataFrame( [market_prior, ret_bl, pd.Series(viewdict)], index=["Prior", "Posterior", "Views"] ).T rets_df.plot.bar(figsize=(12, 8))
💡 后验的直觉验证:画 Prior-Posterior-Views 对比柱状图,后验值总是介于 Prior 和 Views 之间——这是 BL 贝叶斯加权的几何体现。如果后验 = Prior,说明你观点置信度太低被完全稀释;如果后验 = Views,说明你置信度过高,等于硬塞观点。
from pypfopt import EfficientFrontier, objective_functions, DiscreteAllocation ef = EfficientFrontier(ret_bl, S_bl) ef.add_objective(objective_functions.L2_reg) # L2 正则让权重更分散 ef.max_sharpe() weights = ef.clean_weights() pd.Series(weights).plot.pie(figsize=(10, 10))
如果你想跳过 EF,直接用 BL 隐含权重:
bl.bl_weights(delta) # 隐含权重 w = (δΣ)⁻¹ E(R) weights = bl.clean_weights()
bl_weights 是「给定后验收益,最优均值-方差权重」的解析解,不调用 cvxpy,适合快速调试。
mu_historical = expected_returns.mean_historical_return(prices) # 用历史均值做 max_sharpe ef_hist = EfficientFrontier(mu_historical, S) ef_hist.add_objective(objective_functions.L2_reg) ef_hist.max_sharpe() weights_hist = ef_hist.clean_weights() # 对比 compare = pd.DataFrame( [weights_hist, weights], index=["历史均值", "BL后验"] ).T compare.plot.bar(figsize=(12, 6))
| 对比维度 | 纯历史均值 | BL 后验 |
|---|---|---|
| 权重稳定性 | 差(样本敏感) | 好(市场共识) |
| 极端权重 | 多(动不动 60%/0%) | 少(更分散) |
| 收益估计来源 | 单一(历史) | 多源(市场+观点) |
| 样本依赖 | 强 | 弱 |
| 投资直觉 | 「过去 = 未来」 | 「市场 + 我的判断」 |
⚠️ L2 正则的辅助:即便用 BL,有时仍会出现极端权重(尤其当观点置信度极不平衡时)。配合
L2_reg让权重更分散,是工程上的稳健做法。
把连续权重转成实际买入股数(第 9 章详讲):
da = DiscreteAllocation(weights, prices.iloc[-1], total_portfolio_value=20000) alloc, leftover = da.lp_portfolio() print(f"Leftover: ${leftover:.2f}") print(alloc)
bl_returns/bl_cov → 喂 EF 出权重。pi="market" 是语法糖:把 market_implied_prior_returns 内联进构造函数,需配合 market_caps= 和 risk_aversion=。ef.add_objective(objective_functions.L2_reg) 让权重更分散,工程上稳健。w=(δΣ)⁻¹E(R) 出权重,适合快速调试。至此第 7 章讲完。下一章我们看两种完全不同的优化器——HRP 用层次聚类绕开协方差,CLA 用临界线算法精确求解前沿拐点。