Skip to content

组合优化基础

引言

"不要把所有鸡蛋放在一个篮子里"——这句古老的谚语几乎每个投资者都听过。但在量化投资中,分散投资远不止是一句格言,它有着坚实的数学基础。通过科学地构建投资组合,你可以在不降低预期收益的前提下显著降低风险,或者在保持风险不变的前提下提高收益。

本文将从数学原理出发,讲解投资组合构建的核心概念、常用方法和实操技巧,帮助你从"选一只好股票"进化到"构建一个好组合"。


一、为什么需要构建投资组合

1.1 单只股票的风险问题

假设你把全部资金投入一只股票。你的收益完全取决于这一只股票的表现。如果这家公司出了问题(财务造假、行业政策变化、管理层丑闻等),你的全部资金都将遭受损失。

举个极端的例子:如果你在 2020 年底全仓买入某只后来被ST的股票,可能面临 50% 甚至更大的亏损。而如果你同时持有 20 只股票,即使其中一只暴雷,对整体组合的影响也只有 5% 左右。

1.2 系统性风险与非系统性风险

投资风险可以分为两类:

  • 系统性风险(Systematic Risk,又称市场风险):由宏观经济因素引起的、影响整个市场的风险。例如经济衰退、利率变动、战争等。系统性风险无法通过分散投资消除。
  • 非系统性风险(Unsystematic Risk,又称特质风险):由个别公司或行业的特定因素引起的风险。例如公司业绩不及预期、行业政策变化、管理层变动等。非系统性风险可以通过分散投资来降低。

分散投资的核心作用就是消除非系统性风险。

1.3 分散化的数学证明

Harry Markowitz 在 1952 年用数学证明了分散投资的价值,并因此获得了诺贝尔经济学奖。其核心思想是:组合的风险不等于各资产风险的简单加总,而是取决于资产之间的相关性。

考虑一个由 N 只等权重股票组成的组合:

  • 每只股票的平均方差为 σ²
  • 股票之间的平均协方差为 σij
  • 组合的方差为:
σ²_portfolio = (1/N) × σ² + (1 - 1/N) × σ_ij

当 N 趋近于无穷大时:

σ²_portfolio → σ_ij (趋近于平均协方差)

这意味着,随着组合中股票数量的增加,每只股票自身的方差(非系统性风险)的影响逐渐消失,组合的风险趋近于股票之间的平均协方差(系统性风险)。

1.4 多少只股票才够

学术研究表明,持有 15-30 只股票就能消除大部分非系统性风险:

python
import numpy as np

def diversification_benefit(n_stocks, avg_variance=0.04, avg_covariance=0.01):
    """
    计算不同股票数量下组合的总方差

    参数:
        n_stocks: 持有的股票数量
        avg_variance: 单只股票的平均方差(年化)
        avg_covariance: 股票间的平均协方差(年化)

    返回:
        float: 组合波动率
        float: 非系统性风险占比
    """
    portfolio_variance = (1 / n_stocks) * avg_variance + \
                         (1 - 1 / n_stocks) * avg_covariance
    # 组合波动率
    portfolio_vol = np.sqrt(portfolio_variance)
    # 非系统性风险占比
    unsystematic = (1 / n_stocks) * avg_variance
    unsystematic_ratio = unsystematic / portfolio_variance

    return portfolio_vol, unsystematic_ratio

print("股票数量 | 组合波动率 | 非系统性风险占比")
print("-" * 45)
for n in [1, 3, 5, 10, 15, 20, 30, 50, 100]:
    vol, ratio = diversification_benefit(n)
    print(f"  {n:3d}     |  {vol:.2%}     |  {ratio:.1%}")

典型输出:

股票数量 | 组合波动率 | 非系统性风险占比
---------------------------------------------
    1     |  20.00%     |  100.0%
    3     |  14.14%     |  33.3%
    5     |  12.25%     |  20.0%
   10     |  11.18%     |  10.0%
   15     |  10.82%     |  6.7%
   20     |  10.62%     |  5.0%
   30     |  10.41%     |  3.3%
   50     |  10.25%     |  2.0%
  100     |  10.13%     |  1.0%

可以看到,从 1 只到 15 只,风险大幅下降;但超过 30 只后,边际改善已经非常有限。因此,持有 15-30 只股票是一个合理的分散化目标。


二、相关性:分散投资的关键

2.1 相关系数的意义

相关系数(Correlation Coefficient)衡量两个资产收益率之间的线性关系,取值范围从 -1 到 +1:

  • +1:完全正相关,两者同涨同跌,分散无效。
  • 0:不相关,一个的涨跌与另一个无关。
  • -1:完全负相关,一个涨另一个跌,分散效果最佳。

相关系数越低,分散投资的效果越好。

2.2 计算相关系数矩阵

python
import pandas as pd
import numpy as np

def compute_correlation_matrix(price_df):
    """
    计算多只股票之间的相关系数矩阵

    参数:
        price_df: DataFrame, 列为股票代码,值为收盘价

    返回:
        DataFrame: 相关系数矩阵
    """
    # 计算日收益率
    returns = price_df.pct_change().dropna()

    # 计算相关系数矩阵
    corr_matrix = returns.corr()

    return corr_matrix

# 计算平均相关系数
def average_correlation(corr_matrix):
    """计算平均相关系数(排除对角线)"""
    n = len(corr_matrix)
    # 取上三角矩阵(不含对角线)
    mask = np.triu(np.ones((n, n), dtype=bool), k=1)
    avg_corr = corr_matrix.values[mask].mean()
    return avg_corr

2.3 A股市场的相关性特征

A股市场有一些独特的相关性特征需要了解:

  • 同行业股票相关性高:同属一个行业的股票,受行业政策和景气度影响,走势往往高度相关。例如银行股之间的相关性通常在 0.7-0.9。
  • 大小盘风格切换:大盘股和小盘股之间存在"跷跷板效应",某些时期大盘股表现好,某些时期小盘股表现好。这种低相关性为分散投资提供了机会。
  • A股整体相关系数偏高:A股市场个股之间的平均相关系数约为 0.4-0.6,高于美股的 0.3-0.4。这意味着A股市场分散化的效果相对有限,需要更加注意跨行业和跨风格的分散。

2.4 跨资产分散

为了获得更好的分散效果,可以考虑跨资产配置:

资产类别与A股相关性分散效果
国债低/负相关优秀
黄金低相关良好
商品期货低相关良好
海外股票中等相关一般
同行业股票高相关较差

三、常用的组合构建方法

3.1 等权组合(Equal Weight)

最简单的组合方法:每只股票分配相同的权重。

python
def equal_weight_portfolio(n_stocks):
    """
    等权组合

    参数:
        n_stocks: 股票数量

    返回:
        list: 各股权重
    """
    weight = 1.0 / n_stocks
    return [weight] * n_stocks

# 示例:10只股票的等权组合
weights = equal_weight_portfolio(10)
print(f"每只股票权重: {weights[0]:.1%}")
print(f"权重总和: {sum(weights):.1%}")

优点:简单、透明、不需要估计参数。 缺点:没有考虑各股票的风险和收益差异。

等权组合虽然简单,但在学术研究中被反复证明是一种非常有效的策略。它可以被视为一种"反动量"策略——自动地卖出涨得多的股票、买入涨得少的股票(通过再平衡实现),天然具有均值回归的特征。

3.2 市值加权组合(Market-Cap Weight)

按每只股票的流通市值分配权重。这是最常见的指数编制方法(如沪深300指数就是市值加权的)。

python
def market_cap_weight_portfolio(market_caps):
    """
    市值加权组合

    参数:
        market_caps: list, 各股票的流通市值(亿元)

    返回:
        list: 各股权重
    """
    total_cap = sum(market_caps)
    weights = [cap / total_cap for cap in market_caps]
    return weights

# 示例
caps = [5000, 3000, 2000, 1500, 1000, 800, 600, 400, 300, 200]  # 亿元
weights = market_cap_weight_portfolio(caps)
for i, (cap, w) in enumerate(zip(caps, weights)):
    print(f"股票{i+1}: 市值{cap}亿, 权重{w:.1%}")

优点:反映市场的实际结构,容量大,交易成本低(权重自然随市值变化)。 缺点:被大市值股票主导,分散化效果不如等权组合。

3.3 最小方差组合(Minimum Variance Portfolio)

在所有可能的组合中,找到方差(风险)最小的那个:

python
import numpy as np
from scipy.optimize import minimize

def minimum_variance_portfolio(cov_matrix):
    """
    最小方差组合

    参数:
        cov_matrix: np.array, 协方差矩阵

    返回:
        np.array: 最优权重
    """
    n = cov_matrix.shape[0]

    # 目标函数:组合方差
    def portfolio_variance(weights):
        return weights @ cov_matrix @ weights

    # 约束条件:权重之和等于1
    constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}

    # 边界条件:每只股票权重在0到1之间(不允许做空)
    bounds = [(0, 1) for _ in range(n)]

    # 初始猜测:等权
    initial_weights = np.ones(n) / n

    # 优化
    result = minimize(portfolio_variance, initial_weights,
                      method='SLSQP', bounds=bounds,
                      constraints=constraints)

    return result.x

# 示例
n_stocks = 5
# 模拟协方差矩阵
np.random.seed(42)
returns = np.random.randn(1000, n_stocks) * 0.02
cov = np.cov(returns.T)

weights = minimum_variance_portfolio(cov)
print("最小方差组合权重:")
for i, w in enumerate(weights):
    print(f"  股票{i+1}: {w:.2%}")

优点:风险最小的组合,适合保守型投资者。 缺点:可能过度集中于低波动股票,忽略了收益。

3.4 最大夏普比率组合(Tangency Portfolio)

在所有可能的组合中,找到夏普比率(风险调整后收益)最高的那个:

python
def max_sharpe_portfolio(expected_returns, cov_matrix, risk_free_rate=0.03):
    """
    最大夏普比率组合

    参数:
        expected_returns: np.array, 各股票的预期年化收益率
        cov_matrix: np.array, 协方差矩阵
        risk_free_rate: float, 无风险利率

    返回:
        np.array: 最优权重
    """
    n = len(expected_returns)

    def neg_sharpe_ratio(weights):
        port_return = weights @ expected_returns
        port_vol = np.sqrt(weights @ cov_matrix @ weights)
        sharpe = (port_return - risk_free_rate) / port_vol
        return -sharpe  # 取负号因为要最小化

    constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
    bounds = [(0, 1) for _ in range(n)]
    initial_weights = np.ones(n) / n

    result = minimize(neg_sharpe_ratio, initial_weights,
                      method='SLSQP', bounds=bounds,
                      constraints=constraints)

    return result.x

优点:在给定风险水平下获得最大收益,是最优的风险收益平衡。 缺点:对预期收益率的估计非常敏感,输入参数的微小变化可能导致权重大幅变化。

3.5 风险平价组合(Risk Parity)

按各资产的风险贡献相等的原则分配权重:

python
def risk_parity_portfolio(cov_matrix):
    """
    风险平价组合

    参数:
        cov_matrix: np.array, 协方差矩阵

    返回:
        np.array: 最优权重
    """
    n = cov_matrix.shape[0]

    def risk_parity_objective(weights):
        # 各资产的风险贡献
        port_vol = np.sqrt(weights @ cov_matrix @ weights)
        marginal_risk = cov_matrix @ weights
        risk_contribution = weights * marginal_risk / port_vol

        # 目标:各资产的风险贡献尽可能相等
        target_risk = port_vol / n
        return np.sum((risk_contribution - target_risk) ** 2)

    constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
    bounds = [(0.01, 1) for _ in range(n)]  # 避免零权重
    initial_weights = np.ones(n) / n

    result = minimize(risk_parity_objective, initial_weights,
                      method='SLSQP', bounds=bounds,
                      constraints=constraints)

    return result.x

# 示例:不同波动率的资产
volatilities = [0.10, 0.15, 0.20, 0.25, 0.30]  # 年化波动率
# 构造对角协方差矩阵(假设不相关)
cov = np.diag([v**2 for v in volatilities])

weights = risk_parity_portfolio(cov)
print("风险平价组合:")
for i, (vol, w) in enumerate(zip(volatilities, weights)):
    rc = w * (cov[i, i] * w) / np.sqrt(w @ cov @ w)
    print(f"  资产{i+1}: 波动率{vol:.0%}, 权重{w:.1%}, 风险贡献{rc:.2%}")

风险平价的核心思想是:波动率低的资产分配更多资金,波动率高的资产分配更少资金,使每类资产对组合总风险的贡献相等。这种方法的实际效果通常很好,因为它自动实现了分散化。

3.6 方法比较与选择

方法需要估计的参数鲁棒性适用场景
等权最高初学者、快速验证
市值加权市值数据指数投资、大规模资金
最小方差协方差矩阵中高保守型投资者
最大夏普收益率+协方差较低预期收益估计可靠时
风险平价协方差矩阵中高多资产配置

建议:从等权组合开始,逐步尝试更复杂的方法。等权组合的表现通常不会差于复杂的优化方法,因为优化方法的额外收益往往被参数估计误差所抵消。


四、再平衡(Rebalancing)

4.1 为什么需要再平衡

组合构建完成后,随着时间推移,各资产的权重会因为价格变动而偏离目标值。例如:

  • 初始等权组合:每只股票 10%
  • 一段时间后:涨得好的股票可能变成 15%,跌了的可能变成 5%

再平衡就是定期将各资产的权重调整回目标值,卖出涨得多的、买入跌得多的。

4.2 再平衡的频率

python
def rebalance_strategy(weights_current, weights_target, threshold=0.05):
    """
    判断是否需要再平衡

    参数:
        weights_current: 当前权重
        weights_target: 目标权重
        threshold: 偏离阈值(如5%)

    返回:
        bool: 是否需要再平衡
        list: 需要调整的幅度
    """
    deviations = [abs(c - t) for c, t in zip(weights_current, weights_target)]
    max_deviation = max(deviations)
    need_rebalance = max_deviation > threshold

    if need_rebalance:
        adjustments = [t - c for c, t in zip(weights_current, weights_target)]
        return True, adjustments
    return False, []

常见的再平衡频率:

  • 月度再平衡:每月初调整一次。适合中低频策略。
  • 季度再平衡:每季度初调整一次。交易成本较低。
  • 阈值再平衡:当某资产权重偏离目标值超过一定阈值(如 5%)时才调整。兼顾效果和成本。

4.3 再平衡的隐性收益

再平衡不仅是一种操作,更是一种系统性的低买高卖策略——在资产价格上涨时卖出部分获利,在资产价格下跌时逢低加仓。这种纪律性的操作,能在长期中带来额外的收益。

但要注意:再平衡也有成本。频繁交易会产生手续费和冲击成本。需要权衡再平衡带来的收益和交易成本之间的关系。

4.4 再平衡对 A 股的特殊考虑

A股的 T+1 交易制度和涨跌停限制,会影响再平衡的操作:

  • 如果需要卖出的股票处于跌停状态,无法卖出,需要等到跌停打开后再操作。
  • T+1 意味着今天买入的股票明天才能卖出,这限制了日内再平衡的可能。
  • 大盘股的流动性更好,适合更频繁的再平衡;小盘股流动性较差,适合降低再平衡频率。

五、组合绩效评估

5.1 评估指标

构建好组合后,需要用系统的指标来评估其表现:

python
def portfolio_metrics(returns, risk_free_rate=0.03):
    """
    计算组合的常用评估指标

    参数:
        returns: pd.Series, 日收益率序列
        risk_free_rate: float, 年化无风险利率

    返回:
        dict: 各项指标
    """
    annual_return = (1 + returns).prod() ** (252 / len(returns)) - 1
    annual_vol = returns.std() * np.sqrt(252)
    sharpe = (annual_return - risk_free_rate) / annual_vol

    # 最大回撤
    cum_nav = (1 + returns).cumprod()
    cum_max = cum_nav.cummax()
    drawdown = (cum_nav - cum_max) / cum_max
    max_dd = drawdown.min()

    # 卡尔马比率
    calmar = annual_return / abs(max_dd) if max_dd != 0 else 0

    metrics = {
        '年化收益率': f'{annual_return:.2%}',
        '年化波动率': f'{annual_vol:.2%}',
        '夏普比率': f'{sharpe:.2f}',
        '最大回撤': f'{max_dd:.2%}',
        '卡尔马比率': f'{calmar:.2f}',
    }

    return metrics

# 使用示例
# metrics = portfolio_metrics(daily_returns)
# for k, v in metrics.items():
#     print(f'{k}: {v}')

5.2 组合与单资产的对比

评估组合构建是否有效,最直接的方法是比较组合与单资产的表现:

python
def compare_portfolio_vs_individual(returns_df, portfolio_weights):
    """
    比较组合与各单资产的表现

    参数:
        returns_df: pd.DataFrame, 各资产的日收益率
        portfolio_weights: list, 组合权重
    """
    # 计算组合收益率
    port_returns = (returns_df * portfolio_weights).sum(axis=1)

    print("=" * 60)
    print(f"{'资产':>8} | {'年化收益':>8} | {'年化波动':>8} | {'夏普':>6} | {'最大回撤':>8}")
    print("-" * 60)

    # 组合
    metrics = portfolio_metrics(port_returns)
    print(f"{'组合':>8} | {metrics['年化收益率']:>8} | "
          f"{metrics['年化波动率']:>8} | {metrics['夏普比率']:>6} | "
          f"{metrics['最大回撤']:>8}")

    # 各单资产
    for col in returns_df.columns:
        single_metrics = portfolio_metrics(returns_df[col])
        print(f"{col:>8} | {single_metrics['年化收益率']:>8} | "
              f"{single_metrics['年化波动率']:>8} | {single_metrics['夏普比率']:>6} | "
              f"{single_metrics['最大回撤']:>8}")

六、实操建议

6.1 从简单开始

不要一开始就追求最优组合。建议的渐进路径:

  1. 第一步:用等权方法构建一个 10-20 只股票的组合。
  2. 第二步:观察组合的波动和回撤,理解分散化的效果。
  3. 第三步:尝试按行业分散,确保组合不会集中在单一行业。
  4. 第四步:尝试风险平价方法,让高风险和低风险资产平衡。
  5. 第五步:在积累足够经验后,再尝试马科维茨优化等更复杂的方法。

6.2 注意A股的特殊性

  • 涨跌停限制:A股有10%的涨跌停限制(ST股5%,创业板和科创板20%),这会影响组合的再平衡操作。
  • T+1交易:当天买入的股票次日才能卖出,限制了日内再平衡的可能。
  • 行业集中:A股金融板块权重很大,单纯按市值加权会导致组合过度集中于金融股。
  • 政策影响:行业政策变化可能同时影响整个板块,同行业分散效果有限。

6.3 避免过度分散

分散投资不是越多越好。持有 100 只股票的组合,管理成本和交易成本都很高,但风险分散效果仅比 30 只股票好一点点。15-30 只股票是一个合理的范围。

同时,要避免"伪分散"——持有大量但高度相关的股票。例如,持有 20 只银行股,分散效果远不如持有 5 只不同行业的龙头股。


七、总结

投资组合构建是量化投资的核心技能之一。关键要点如下:

  1. 分散投资有数学基础:Markowitz 的现代投资组合理论证明了,通过组合不完全相关的资产,可以在不降低预期收益的前提下降低风险。
  2. 相关性是关键:组合中资产的相关性越低,分散效果越好。要注重跨行业、跨风格的分散。
  3. 简单方法往往足够好:等权组合的表现通常不输给复杂的优化方法,而且更加稳健。
  4. 再平衡是纪律的体现:定期再平衡不仅维护了目标权重,还实现了系统性的低买高卖。
  5. 避免过度分散:15-30 只不同行业的股票就能消除大部分非系统性风险。

记住,构建投资组合的目标不是追求最高的收益,而是在可承受的风险范围内获得稳健的回报。在投资的马拉松中,稳健比速度更重要。

仅供学习交流,不构成任何投资建议