Appearance
组合优化基础
引言
"不要把所有鸡蛋放在一个篮子里"——这句古老的谚语几乎每个投资者都听过。但在量化投资中,分散投资远不止是一句格言,它有着坚实的数学基础。通过科学地构建投资组合,你可以在不降低预期收益的前提下显著降低风险,或者在保持风险不变的前提下提高收益。
本文将从数学原理出发,讲解投资组合构建的核心概念、常用方法和实操技巧,帮助你从"选一只好股票"进化到"构建一个好组合"。
一、为什么需要构建投资组合
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_corr2.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 从简单开始
不要一开始就追求最优组合。建议的渐进路径:
- 第一步:用等权方法构建一个 10-20 只股票的组合。
- 第二步:观察组合的波动和回撤,理解分散化的效果。
- 第三步:尝试按行业分散,确保组合不会集中在单一行业。
- 第四步:尝试风险平价方法,让高风险和低风险资产平衡。
- 第五步:在积累足够经验后,再尝试马科维茨优化等更复杂的方法。
6.2 注意A股的特殊性
- 涨跌停限制:A股有10%的涨跌停限制(ST股5%,创业板和科创板20%),这会影响组合的再平衡操作。
- T+1交易:当天买入的股票次日才能卖出,限制了日内再平衡的可能。
- 行业集中:A股金融板块权重很大,单纯按市值加权会导致组合过度集中于金融股。
- 政策影响:行业政策变化可能同时影响整个板块,同行业分散效果有限。
6.3 避免过度分散
分散投资不是越多越好。持有 100 只股票的组合,管理成本和交易成本都很高,但风险分散效果仅比 30 只股票好一点点。15-30 只股票是一个合理的范围。
同时,要避免"伪分散"——持有大量但高度相关的股票。例如,持有 20 只银行股,分散效果远不如持有 5 只不同行业的龙头股。
七、总结
投资组合构建是量化投资的核心技能之一。关键要点如下:
- 分散投资有数学基础:Markowitz 的现代投资组合理论证明了,通过组合不完全相关的资产,可以在不降低预期收益的前提下降低风险。
- 相关性是关键:组合中资产的相关性越低,分散效果越好。要注重跨行业、跨风格的分散。
- 简单方法往往足够好:等权组合的表现通常不输给复杂的优化方法,而且更加稳健。
- 再平衡是纪律的体现:定期再平衡不仅维护了目标权重,还实现了系统性的低买高卖。
- 避免过度分散:15-30 只不同行业的股票就能消除大部分非系统性风险。
记住,构建投资组合的目标不是追求最高的收益,而是在可承受的风险范围内获得稳健的回报。在投资的马拉松中,稳健比速度更重要。