01-组合构建理论 (Portfolio Construction)

预计学习时间:3 小时

难度:⭐⭐⭐⭐

核心问题:怎么把一堆信号变成一个具体的持仓组合?


从一个直觉出发

假设你的量化模型对 500 只股票分别给出了预期收益率预测:

贵州茅台: 预期涨 5%
宁德时代: 预期涨 8%
中国平安: 预期涨 3%
...
ST 某某:   预期涨 15%  ← 预期收益最高,但风险极大

你怎么分配资金?

最直觉的方案:把钱全押在预期收益最高的那只上。

但问题来了:

  1. 你的预测可能是错的
  2. 高预期收益往往伴随高风险
  3. 如果所有高预期收益的股票都是同一行业,遇到行业黑天鹅就全完了

你需要一个系统化的框架来回答”每只股票买多少”这个问题。

这个框架就是组合理论


一、从单个信号到组合:为什么需要组合理论

1.1 单个信号的局限

一个 Alpha 信号告诉你”某只股票看多”或”看空”。但信号不是确定性知识,它只是一种概率判断。

单个信号 → 仓位 → 收益

问题:
  1. 信号可能错(误判风险)
  2. 单个仓位波动太大(集中度风险)
  3. 多个信号之间可能有相关性(风险重叠)
  4. 资金有限(预算约束)

1.2 分散化的力量

分散化是金融学中最接近”免费午餐”的概念。核心直觉:

如果你有 10 个独立且期望为正的赌局,同时下注 10 个比单独下注 1 个的风险要小得多。

当资产之间的相关性小于 1 时,组合波动率小于各资产波动率的加权平均。这就是分散化的数学基础。

import numpy as np
import matplotlib.pyplot as plt
 
np.random.seed(42)
 
# 演示分散化效应
n_simulations = 10000
n_days = 252
 
# 单只股票:年化收益 10%,年化波动率 30%
mu = 0.10 / 252
sigma = 0.30 / np.sqrt(252)
 
# 模拟单只股票的一年收益
single_stock = np.exp(np.cumsum(np.random.normal(mu, sigma, (n_simulations, n_days)), axis=1))
single_returns = single_stock[:, -1] - 1
 
# 模拟等权组合,资产数量从 1 到 50
portfolio_sizes = [1, 2, 5, 10, 20, 50]
results = {'size': [], 'mean': [], 'std': [], 'sharpe': []}
 
for n in portfolio_sizes:
    # 模拟 n 只独立股票
    stocks = np.exp(np.cumsum(
        np.random.normal(mu, sigma, (n_simulations, n_days, n)),
        axis=1
    ))
    # 等权组合
    equal_weight = 1.0 / n
    portfolio = np.sum(stocks[:, -1, :] * equal_weight, axis=1) - 1
 
    results['size'].append(n)
    results['mean'].append(np.mean(portfolio))
    results['std'].append(np.std(portfolio))
    results['sharpe'].append(np.mean(portfolio) / np.std(portfolio))
 
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
 
axes[0].bar([str(s) for s in results['size']], results['std'],
            color='steelblue', alpha=0.7)
axes[0].set_xlabel('组合中股票数量')
axes[0].set_ylabel('年化波动率')
axes[0].set_title('分散化效应:股票越多,波动率越低')
axes[0].grid(True, alpha=0.3)
 
# 理论值:sigma_p = sigma / sqrt(n)(独立资产)
theoretical_std = 0.30 / np.sqrt(np.array(portfolio_sizes))
axes[0].plot(range(len(portfolio_sizes)), theoretical_std,
             'ro-', label='理论值 (独立资产)')
axes[0].legend()
 
axes[1].bar([str(s) for s in results['size']], results['sharpe'],
            color='darkgreen', alpha=0.7)
axes[1].set_xlabel('组合中股票数量')
axes[1].set_ylabel('Sharpe 比率')
axes[1].set_title('分散化效应:Sharpe 比率提升')
axes[1].grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

关键洞察:分散化不能提高预期收益,但可以大幅降低风险,从而提高风险调整后收益(Sharpe 比率)。


二、Markowitz 均值方差模型

2.1 核心思想

Harry Markowitz 在 1952 年提出:投资者应该在给定风险下最大化预期收益,或在给定预期收益下最小化风险

其中:

  • :权重向量(
  • :协方差矩阵(
  • :预期收益向量(
  • :目标预期收益
  • :全 1 向量

2.2 数学推导(拉格朗日方法)

引入拉格朗日乘子

求偏导并令其为零:

这就是 Markowitz 模型的解析解。它告诉我们:最优权重是协方差矩阵的逆与预期收益的线性组合的乘积

2.3 有效前沿

对于不同的目标收益 ,求解上述优化问题,得到的 的轨迹就是有效前沿

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
 
np.random.seed(42)
 
# ============================================================
# Markowitz 均值方差模型
# ============================================================
 
def simulate_assets(n_assets, n_days=252, mu_base=0.08, sigma_base=0.25):
    """模拟 n 只资产的一年日度数据"""
    returns = np.zeros((n_days, n_assets))
    for i in range(n_assets):
        mu = mu_base + np.random.normal(0, 0.05)
        sigma = sigma_base + np.random.normal(0, 0.08)
        sigma = max(sigma, 0.10)  # 确保波动率为正
        returns[:, i] = np.random.normal(mu / 252, sigma / np.sqrt(252), n_days)
 
    return returns
 
# 模拟 10 只资产
n_assets = 10
returns = simulate_assets(n_assets)
 
# 估计参数
mu = returns.mean(axis=0) * 252          # 年化预期收益
Sigma = np.cov(returns.T) * 252          # 年化协方差矩阵
 
def portfolio_variance(w, Sigma):
    """组合方差"""
    return w @ Sigma @ w
 
def portfolio_return(w, mu):
    """组合预期收益"""
    return w @ mu
 
def markowitz_optimize(mu_target, mu, Sigma, short_allowed=False):
    """
    Markowitz 均值方差优化
 
    参数:
        mu_target: 目标预期收益
        mu: 预期收益向量
        Sigma: 协方差矩阵
        short_allowed: 是否允许做空
    """
    n = len(mu)
 
    constraints = [
        {'type': 'eq', 'fun': lambda w: np.sum(w) - 1},  # 权重之和 = 1
        {'type': 'eq', 'fun': lambda w: w @ mu - mu_target}  # 达到目标收益
    ]
 
    bounds = None if short_allowed else [(0, 1) for _ in range(n)]
 
    # 初始值:等权
    w0 = np.ones(n) / n
 
    result = minimize(
        portfolio_variance, w0,
        args=(Sigma,),
        method='SLSQP',
        bounds=bounds,
        constraints=constraints,
        options={'maxiter': 1000, 'ftol': 1e-12}
    )
 
    return result.x if result.success else None
 
# ============================================================
# 计算有效前沿
# ============================================================
n_points = 50
mu_min = mu.min()
mu_max = mu.max()
mu_targets = np.linspace(mu_min + 0.01, mu_max - 0.01, n_points)
 
efficient_portfolios = []
for target in mu_targets:
    w = markowitz_optimize(target, mu, Sigma, short_allowed=False)
    if w is not None:
        port_ret = portfolio_return(w, mu)
        port_vol = np.sqrt(portfolio_variance(w, Sigma))
        efficient_portfolios.append((port_vol, port_ret, w))
 
efficient_portfolios = np.array(efficient_portfolios)
 
# 全局最小方差组合(GMV)
def gmv_portfolio(Sigma):
    """全局最小方差组合"""
    n = len(Sigma)
    constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
    bounds = [(0, 1) for _ in range(n)]
    w0 = np.ones(n) / n
 
    result = minimize(
        portfolio_variance, w0,
        args=(Sigma,),
        method='SLSQP',
        bounds=bounds,
        constraints=constraints
    )
    return result.x
 
w_gmv = gmv_portfolio(Sigma)
gmv_ret = portfolio_return(w_gmv, mu)
gmv_vol = np.sqrt(portfolio_variance(w_gmv, Sigma))
 
# 可视化
fig, ax = plt.subplots(figsize=(12, 7))
 
# 有效前沿
ax.plot(efficient_portfolios[:, 0], efficient_portfolios[:, 1],
        'b-', linewidth=2, label='有效前沿')
 
# GMV 点
ax.scatter(gmv_vol, gmv_ret, c='red', s=100, zorder=5,
           label=f'GMV (vol={gmv_vol:.2%}, ret={gmv_ret:.2%})')
 
# 等权组合
w_eq = np.ones(n_assets) / n_assets
eq_ret = portfolio_return(w_eq, mu)
eq_vol = np.sqrt(portfolio_variance(w_eq, Sigma))
ax.scatter(eq_vol, eq_ret, c='green', s=100, zorder=5,
           label=f'等权 (vol={eq_vol:.2%}, ret={eq_ret:.2%})')
 
# 单个资产
ax.scatter(np.sqrt(np.diag(Sigma)), mu, c='gray', s=30, alpha=0.5,
           label='单个资产')
 
# 资本市场线(从无风险利率到最大 Sharpe 比率组合)
rf = 0.02  # 无风险利率
sharpe_ratios = (efficient_portfolios[:, 1] - rf) / efficient_portfolios[:, 0]
tangent_idx = np.argmax(sharpe_ratios)
tangent_vol = efficient_portfolios[tangent_idx, 0]
tangent_ret = efficient_portfolios[tangent_idx, 1]
 
cml_x = np.linspace(0, tangent_vol * 1.5, 50)
cml_y = rf + (tangent_ret - rf) / tangent_vol * cml_x
ax.plot(cml_x, cml_y, 'r--', linewidth=1.5,
        label=f'资本市场线 (Sharpe={sharpe_ratios[tangent_idx]:.2f})')
 
ax.set_xlabel('年化波动率')
ax.set_ylabel('年化预期收益')
ax.set_title('Markowitz 有效前沿')
ax.legend()
ax.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()
 
print(f"=== 组合对比 ===")
print(f"等权组合: 波动率={eq_vol:.2%}, 收益={eq_ret:.2%}, Sharpe={sharpe_ratios.mean():.2f}")
print(f"GMV组合: 波动率={gmv_vol:.2%}, 收益={gmv_ret:.2%}")
print(f"最大Sharpe: 波动率={tangent_vol:.2%}, 收益={tangent_ret:.2%}, "
      f"Sharpe={sharpe_ratios[tangent_idx]:.2f}")

2.4 均值方差模型的致命问题

Markowitz 模型在理论上很优美,但在实践中有一个致命缺陷:对输入参数极度敏感

均值方差模型的问题:

  1. 估计误差放大效应
     → Sigma^{-1} 的逆运算会放大估计误差
     → 微小的协方差估计变化 → 巨大的权重变化

  2. 收益率估计极度不准确
     → 年化预期收益的估计误差通常大于预期收益本身
     → 输入垃圾,输出垃圾(GIGO)

  3. 极端权重
     → 优化器倾向于给出 90% 某只股票、-50% 另一只股票的极端权重
     → 在实际中不可执行

  4. 集中在少数资产
     → 有效前沿上的最优组合往往只持有少数几只股票
     → 与"分散化"的初衷相悖

经验法则:在 只股票上做 Markowitz 优化,你需要至少 个独立数据点才能可靠估计协方差矩阵。但金融市场的不平稳性意味着历史数据不一定代表未来。


三、Black-Litterman 模型

3.1 核心思想

Black & Litterman (1992) 提出的问题是:与其直接估计每只股票的预期收益(这几乎不可能),不如从”市场均衡”出发,然后把你的”观点”融合进去。

Markowitz 的做法:
  估计每只股票的 mu → 输入优化器 → 得到权重
  问题:mu 的估计极度不准确

Black-Litterman 的做法:
  从市场均衡出发 → 加入投资者观点 → 得到"后验"预期收益 → 输入优化器
  优势:后验收益比直接估计更稳定

3.2 数学框架

第一步:市场均衡收益

从 CAPM 出发,如果所有投资者持有市场组合,那么资产的均衡预期收益为:

其中:

  • :隐含均衡收益向量
  • :风险厌恶系数(通常取
  • :市场权重(市值加权)
  • :协方差矩阵

第二步:融入投资者观点

投资者有 个观点,用矩阵 和向量 表示:

其中:

  • 的”观点矩阵”(每行是一个观点,指出涉及哪些资产)
  • 的观点收益向量(每个观点的具体收益预期)
  • 是观点的不确定性

第三步:贝叶斯更新

后验预期收益:

其中 是衡量均衡收益不确定性的标量(通常取 0.025-0.05), 是观点不确定性矩阵。

import numpy as np
 
np.random.seed(42)
 
# ============================================================
# Black-Litterman 模型
# ============================================================
 
def black_litterman(Sigma, w_mkt, P, Q, Omega, delta=2.5, tau=0.05):
    """
    Black-Litterman 模型
 
    参数:
        Sigma: 协方差矩阵 (n x n)
        w_mkt: 市场权重 (n,)
        P: 观点矩阵 (K x n)
        Q: 观点收益 (K,)
        Omega: 观点不确定性 (K x K)
        delta: 风险厌恶系数
        tau: 均衡收益不确定性标量
 
    返回:
        mu_bl: 后验预期收益
        Sigma_bl: 后验协方差矩阵
    """
    n = len(w_mkt)
 
    # 第一步:隐含均衡收益
    Pi = delta * Sigma @ w_mkt
 
    # 第二步:贝叶斯更新
    tau_Sigma_inv = np.linalg.inv(tau * Sigma)
    Omega_inv = np.linalg.inv(Omega)
 
    # 后验协方差
    M = np.linalg.inv(tau_Sigma_inv + P.T @ Omega_inv @ P)
    Sigma_bl = Sigma + M  # 后验协方差
 
    # 后验预期收益
    mu_bl = M @ (tau_Sigma_inv @ Pi + P.T @ Omega_inv @ Q)
 
    return mu_bl, Sigma_bl
 
# 模拟:5 只资产
n_assets = 5
Sigma = np.array([
    [0.0400, 0.0060, 0.0020, 0.0010, 0.0005],
    [0.0060, 0.0300, 0.0040, 0.0020, 0.0010],
    [0.0020, 0.0040, 0.0500, 0.0030, 0.0015],
    [0.0010, 0.0020, 0.0030, 0.0600, 0.0040],
    [0.0005, 0.0010, 0.0015, 0.0040, 0.0700],
])
 
# 市场权重(市值加权)
w_mkt = np.array([0.30, 0.25, 0.20, 0.15, 0.10])
 
# 投资者观点
# 观点 1:资产 1 比市场预期多涨 3%
# 观点 2:资产 3 比资产 2 多涨 2%(相对观点)
P = np.array([
    [1, 0, 0, 0, 0],    # 绝对观点:资产 1
    [0, -1, 1, 0, 0],    # 相对观点:资产 3 - 资产 2
])
Q = np.array([0.03, 0.02])
 
# 观点不确定性(对角矩阵,值越小表示越确信)
Omega = np.diag([0.01, 0.01])
 
# 计算 Black-Litterman 后验收益
mu_bl, Sigma_bl = black_litterman(Sigma, w_mkt, P, Q, Omega)
 
# 对比
Pi = 2.5 * Sigma @ w_mkt  # 均衡收益
 
print("=== 隐含均衡收益 vs Black-Litterman 后验收益 ===")
for i in range(n_assets):
    print(f"资产 {i+1}: 均衡={Pi[i]:.4f}, BL后验={mu_bl[i]:.4f}, "
          f"差异={mu_bl[i]-Pi[i]:.4f}")
=== 隐含均衡收益 vs Black-Litterman 后验收益 ===
资产 1: 均衡=0.0385, BL后验=0.0643, 差异=0.0258
资产 2: 均衡=0.0280, BL后验=0.0249, 差异=-0.0031
资产 3: 均衡=0.0325, BL后验=0.0426, 差异=0.0101
资产 4: 均衡=0.0330, BL后验=0.0330, 差异=0.0000
资产 5: 均衡=0.0365, BL后验=0.0365, 差异=0.0000

3.3 Black-Litterman 的优势

维度MarkowitzBlack-Litterman
收益估计直接估计(极度不准确)市场均衡 + 观点修正(更稳定)
输入敏感度极度敏感相对稳健
权重分布常出现极端权重更合理的权重分布
允许观点不明确可以融入投资者的具体观点
直觉性高(“我认为 X 比 Y 多涨 2%“)

四、风险平价 (Risk Parity)

4.1 核心思想

风险平价的思想来自一个非常简单的观察:

在传统 60/40 股债组合中,虽然资金是 60:40,但风险的贡献是 80:20(甚至更极端)。

为什么?因为股票的波动率远高于债券。

风险平价的目标:让每个资产对组合总风险的贡献相等。

传统组合(60% 股 / 40% 债):
  资金配比:   股 60%  |  债 40%
  风险贡献:   股 85%  |  债 15%  ← 不均衡!

风险平价组合:
  资金配比:   股 30%  |  债 70%
  风险贡献:   股 50%  |  债 50%  ← 每个资产风险贡献相等

4.2 数学表达

资产 对组合总风险的边际风险贡献

资产 对组合总风险的风险贡献(Risk Contribution):

风险平价目标:

等价于:

import numpy as np
from scipy.optimize import minimize
 
np.random.seed(42)
 
# ============================================================
# 风险平价模型
# ============================================================
 
def risk_contribution(w, Sigma):
    """
    计算每个资产的风险贡献
 
    返回:
        rc: 各资产的风险贡献 (n,)
        total_risk: 组合总风险(波动率)
    """
    port_var = w @ Sigma @ w
    port_vol = np.sqrt(port_var)
    marginal = Sigma @ w / port_vol
    rc = w * marginal  # 风险贡献 = 权重 x 边际贡献
    return rc, port_vol
 
def risk_parity_objective(w, Sigma):
    """风险平价目标函数:使各资产风险贡献的差异最小化"""
    rc, _ = risk_contribution(w, Sigma)
    n = len(w)
    # 目标:所有 rc_i 相等
    # 用 rc_i / port_var 来归一化
    target = 1.0 / n  # 每个资产贡献 1/n 的风险
    rc_pct = rc / np.sum(rc)  # 百分比形式
    return np.sum((rc_pct - target) ** 2)
 
def solve_risk_parity(Sigma, long_only=True):
    """求解风险平价组合"""
    n = len(Sigma)
    constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
    bounds = [(0, 1) for _ in range(n)] if long_only else None
    w0 = np.ones(n) / n
 
    result = minimize(
        risk_parity_objective, w0,
        args=(Sigma,),
        method='SLSQP',
        bounds=bounds,
        constraints=constraints,
        options={'maxiter': 1000, 'ftol': 1e-15}
    )
 
    return result.x
 
# 模拟股债组合
Sigma_2 = np.array([
    [0.04, 0.001],   # 股票协方差矩阵
    [0.001, 0.01],   # 债券协方差矩阵
])
 
w_rp = solve_risk_parity(Sigma_2)
w_6040 = np.array([0.6, 0.4])
 
rc_rp, vol_rp = risk_contribution(w_rp, Sigma_2)
rc_6040, vol_6040 = risk_contribution(w_6040, Sigma_2)
 
print("=== 60/40 组合 vs 风险平价组合 ===")
print(f"60/40 组合: 权重=[{w_6040[0]:.1%}, {w_6040[1]:.1%}]")
print(f"  风险贡献: [{rc_6040[0]/sum(rc_6040):.1%}, {rc_6040[1]/sum(rc_6040):.1%}]")
print(f"  组合波动率: {vol_6040:.2%}")
print(f"\n风险平价组合: 权重=[{w_rp[0]:.1%}, {w_rp[1]:.1%}]")
print(f"  风险贡献: [{rc_rp[0]/sum(rc_rp):.1%}, {rc_rp[1]/sum(rc_rp):.1%}]")
print(f"  组合波动率: {vol_rp:.2%}")
 
# 多资产风险平价
n_assets = 5
np.random.seed(42)
Sigma_5 = np.random.uniform(0.01, 0.06, (n_assets, n_assets))
Sigma_5 = (Sigma_5 + Sigma_5.T) / 2
np.fill_diagonal(Sigma_5, np.random.uniform(0.03, 0.08, n_assets))
 
w_rp_5 = solve_risk_parity(Sigma_5)
rc_rp_5, vol_rp_5 = risk_contribution(w_rp_5, Sigma_5)
 
print(f"\n=== 多资产风险平价 ===")
for i in range(n_assets):
    print(f"资产 {i+1}: 权重={w_rp_5[i]:.2%}, "
          f"风险贡献={rc_rp_5[i]/sum(rc_rp_5):.2%}")

4.3 风险平价 vs 等权 vs Markowitz

维度等权组合Markowitz风险平价
核心逻辑每个资产分配相同资金最大化风险调整后收益每个资产贡献相同风险
需要收益估计是(极不准确)
需要协方差矩阵
权重稳定性
极端权重常见
分散化质量理论最优(但不可靠)

关键认知:风险平价是 Markowitz 模型的一个实用替代方案。它不需要估计预期收益(这是最难的部分),只需要协方差矩阵,而且产生的权重更稳定。


五、全局最小方差 (GMV)

5.1 核心思想

GMV 是最”保守”的组合构建方法:不关心收益,只关心风险

直觉:如果我不知道哪只股票会涨,但我能估计它们之间的相关性,那我至少可以找到波动率最小的组合。

5.2 解析解

GMV 有解析解:

这就是有效前沿最左边的那一点。

5.3 GMV 的优缺点

优点

  • 不需要估计预期收益(只依赖协方差矩阵)
  • 产生的权重比 Markowitz 更稳定
  • 在实证中表现往往优于均值方差优化

缺点

  • 可能过度集中在低波动资产(如大量持有债券)
  • 完全忽略了收益信息
  • 在低利率环境中,低波动组合的绝对收益可能不够
def gmv_analytical(Sigma):
    """GMV 解析解"""
    Sigma_inv = np.linalg.inv(Sigma)
    ones = np.ones(len(Sigma))
    w = Sigma_inv @ ones / (ones @ Sigma_inv @ ones)
    return w
 
# 对比
w_gmv_analytical = gmv_analytical(Sigma_5)
w_gmv_numerical = gmv_portfolio(Sigma_5)
 
print("=== GMV 解析解 vs 数值解 ===")
print(f"解析解: {w_gmv_analytical}")
print(f"数值解: {w_gmv_numerical}")
print(f"差异: {np.max(np.abs(w_gmv_analytical - w_gmv_numerical)):.8f}")

六、约束优化

6.1 为什么需要约束

在实务中,组合优化几乎不会在”无约束”条件下进行。原因:

实务约束:

  1. 行业中性:不对任何行业过度暴露
     → Σ(某行业权重) ≤ 20%

  2. 个股权重上限:防止过度集中
     → w_i ≤ 5%

  3. 换手率约束:控制交易成本
     → Σ|w_new - w_old| ≤ 20%

  4. 做空限制:许多机构不能做空
     → w_i ≥ 0

  5. 因子暴露约束:控制风格偏移
     → |Σ w_i * factor_i - target| ≤ threshold

6.2 带约束的组合优化

import numpy as np
from scipy.optimize import minimize
 
def constrained_portfolio_optimize(mu, Sigma, w_old=None,
                                  max_weight=0.05,
                                  max_turnover=0.2,
                                  industry_exposure=None,
                                  industry_limits=None):
    """
    带约束的组合优化
 
    参数:
        mu: 预期收益
        Sigma: 协方差矩阵
        w_old: 当前持仓(用于换手率约束)
        max_weight: 个股权重上限
        max_turnover: 最大换手率
        industry_exposure: 行业暴露矩阵 (n_assets x n_industries)
        industry_limits: 行业权重上限 (n_industries,)
    """
    n = len(mu)
    w0 = np.ones(n) / n
 
    # 目标函数:最大化风险调整后收益(等价于最小化负的)
    def neg_sharpe(w):
        port_ret = w @ mu
        port_vol = np.sqrt(w @ Sigma @ w)
        return -port_ret / port_vol if port_vol > 1e-10 else 0
 
    constraints = [
        {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}  # 权重之和 = 1
    ]
 
    # 换手率约束
    if w_old is not None:
        constraints.append({
            'type': 'ineq',
            'fun': lambda w: max_turnover - np.sum(np.abs(w - w_old))
        })
 
    # 行业暴露约束
    if industry_exposure is not None and industry_limits is not None:
        n_ind = industry_limits.shape[0]
        for j in range(n_ind):
            constraints.append({
                'type': 'ineq',
                'fun': lambda w, j=j: industry_limits[j] - w @ industry_exposure[:, j]
            })
 
    bounds = [(0, max_weight) for _ in range(n)]
 
    result = minimize(
        neg_sharpe, w0,
        method='SLSQP',
        bounds=bounds,
        constraints=constraints,
        options={'maxiter': 2000, 'ftol': 1e-12}
    )
 
    return result.x if result.success else None
 
 
# 模拟:10 只股票,3 个行业
np.random.seed(42)
n = 10
mu_sim = np.random.uniform(0.05, 0.20, n)
Sigma_sim = np.eye(n) * 0.04 + np.random.uniform(0.005, 0.02, (n, n)) * 0.5
Sigma_sim = (Sigma_sim + Sigma_sim.T) / 2
 
# 行业分类
industry_matrix = np.zeros((n, 3))
industry_matrix[:4, 0] = 1   # 行业 1:资产 1-4
industry_matrix[4:7, 1] = 1  # 行业 2:资产 5-7
industry_matrix[7:, 2] = 1   # 行业 3:资产 8-10
 
industry_limits = np.array([0.40, 0.40, 0.40])  # 每个行业最多 40%
 
# 当前持仓(用于换手率约束)
w_current = np.ones(n) / n
 
w_new = constrained_portfolio_optimize(
    mu_sim, Sigma_sim,
    w_old=w_current,
    max_weight=0.10,
    max_turnover=0.30,
    industry_exposure=industry_matrix,
    industry_limits=industry_limits
)
 
if w_new is not None:
    print("=== 约束优化结果 ===")
    for i in range(n):
        ind = np.argmax(industry_matrix[i])
        print(f"资产 {i+1} (行业{ind+1}): 权重={w_new[i]:.2%}")
 
    print(f"\n行业暴露:")
    for j in range(3):
        print(f"行业 {j+1}: {w_new @ industry_matrix[:, j]:.2%} "
              f"(上限={industry_limits[j]:.0%})")
 
    turnover = np.sum(np.abs(w_new - w_current))
    print(f"\n换手率: {turnover:.2%}")

6.3 各组合构建方法对比

方法需要收益估计需要协方差权重稳定性分散化实务适用性
等权极高基准对比
Markowitz高(理论)低(估计误差大)
Black-Litterman部分中-高中-高
GMV
风险平价

实务建议

  • 如果你对收益估计有信心(有强大的因子模型),用 Black-Litterman
  • 如果你对收益估计没信心,用风险平价或 GMV
  • 永远加约束(权重上限、换手率限制、行业中性)
  • 用协方差矩阵的稳健估计(下一章的内容)

七、小结

概念要点
分散化核心原理:相关性小于 1 的资产组合可以降低风险
Markowitz在给定风险下最大化收益。理论上优美,实务中因估计误差而难以使用
Black-Litterman从市场均衡出发,融入投资者观点。比 Markowitz 更实用
风险平价每个资产贡献相等风险。不需要收益估计,权重稳定
GMV只关注最小风险。不需要收益估计,解析解可用
约束优化实务必需。权重上限、换手率约束、行业中性

一句话总结:组合构建的核心矛盾是”你想要高收益但预测不准”。Markowitz 告诉你最优是什么样子,但它的前提(准确的预期收益)几乎不可能满足。Black-Litterman、风险平价、GMV 分别从不同角度回避了这个问题,但都需要一个准确的协方差矩阵——这就是下一章的主题。


参考阅读:Markowitz (1952), “Portfolio Selection”; Black & Litterman (1992), “Global Portfolio Optimization”; Maillard, Roncalli & Teiletche (2010), “The Properties of Equally Weighted Risk Contribution Portfolios”

下一章:02-协方差矩阵估计 — 组合理论的基础设施