投资组合构建方法
1. Top-K 均等权重
1.1 算法步骤
def top_k_equal_weight(predictions, k=20):
"""
Top-K 均等权重策略
参数:
predictions: 股票预测分数
k: 选择股票数量
返回:
weights: 股票权重,权重和为1
"""
# 1. 按预测分数排序
sorted_stocks = predictions.sort_values(ascending=False)
# 2. 选择 Top-K
top_k = sorted_stocks[:k]
# 3. 计算等权重
weight = 1.0 / k
# 4. 分配权重
weights = pd.Series(0, index=predictions.index)
weights[top_k.index] = weight
return weights
# 示例
predictions = pd.Series({
'600000': 0.025,
'600001': 0.023,
'600002': 0.010,
'600003': 0.009,
'600004': -0.015,
'600005': -0.020,
})
weights = top_k_equal_weight(predictions, k=3)
print("Top-K 权重分配:")
print(weights)输出:
Top-K 权重分配:
600000 0.333333
600001 0.333333
600002 0.333333
600003 0.000000
600004 0.000000
600005 0.000000
dtype: float64
1.2 关键参数
k(选择股票数量)
- 推荐范围:20-50
- 小 k(10):集中度高,风险大,潜在收益高
- 大 k(50):分散度高,风险低,收益可能降低
- k = 全市场:等权重市场组合,接近指数投资
调仓频率
- 日频:每天调仓,成本高,反应及时
- 周频:每周调仓,平衡成本和及时性
- 月频:每月调仓,成本低,反应滞后
权重方式
- 等权重:简单,适合新手
- 按分数加权:考虑预测差异,但增加复杂度
1.3 适用场景
- ✅ 预测质量高且稳定
- ✅ 追求简单策略
- ✅ 流动性要求高
- ✅ 交易成本敏感
- ✅ 新手入门
1.4 参数敏感性分析
def analyze_k_sensitivity(predictions, k_values=[10, 20, 30, 40, 50]):
"""
分析 K 值敏感性
参数:
predictions: 股票预测分数
k_values: K 值列表
返回:
results: 不同 K 值的结果
"""
results = []
for k in k_values:
weights = top_k_equal_weight(predictions, k=k)
# 计算指标
n_selected = (weights > 0).sum()
avg_weight = weights[weights > 0].mean()
max_weight = weights.max()
results.append({
'k': k,
'n_selected': n_selected,
'avg_weight': avg_weight,
'max_weight': max_weight
})
return pd.DataFrame(results)
# 示例
sensitivity_results = analyze_k_sensitivity(predictions)
print("K 值敏感性分析:")
print(sensitivity_results)2. IC 权重分配
2.1 算法步骤
def ic_weight_strategy(predictions, ic_history, window=20, min_ic=0.02):
"""
IC 权重策略
参数:
predictions: 股票预测分数
ic_history: IC 历史数据
window: IC 计算窗口
min_ic: 最小 IC 阈值
返回:
weights: 股票权重,权重和为1
"""
# 1. 计算滚动 IC
rolling_ic = ic_history.rolling(window=window).mean()
# 2. 只选择 IC > 0 的股票
valid_stocks = rolling_ic[rolling_ic > min_ic]
# 3. 根据 IC 分配权重
if len(valid_stocks) == 0:
# 如果没有符合条件的股票,使用等权重
weights = pd.Series(1.0 / len(predictions), index=predictions.index)
else:
weights = valid_stocks / valid_stocks.sum()
# 4. 填充未选中的股票
final_weights = pd.Series(0, index=predictions.index)
final_weights[weights.index] = weights
return final_weights
# 示例
ic_history = pd.DataFrame({
'600000': [0.03, 0.04, 0.05, 0.04, 0.06],
'600001': [0.02, 0.03, 0.02, 0.03, 0.02],
'600002': [0.01, 0.01, 0.02, 0.01, 0.01],
'600003': [0.00, 0.01, 0.00, 0.01, 0.00],
'600004': [-0.01, -0.02, -0.01, -0.02, -0.01],
})
weights = ic_weight_strategy(predictions, ic_history, window=5, min_ic=0.02)
print("IC 权重分配:")
print(weights)2.2 关键参数
window(IC 计算窗口)
- 推荐范围:20-60
- 窗口小:反应快,但不稳定
- 窗口大:反应慢,但更稳定
min_ic(最小 IC 阈值)
- 推荐范围:0.02-0.05
- 阈值高:选股更严格,可能仓位不足
- 阈值低:选股宽松,可能包含低质量股票
smooth_factor(IC 平滑系数)
- 推荐范围:0.5-2.0
- 用于平滑 IC 变动
weight_cap(最大单股权重)
- 推荐范围:0.05-0.2
- 限制单股权重,控制风险
2.3 适用场景
- ✅ 有足够历史数据
- ✅ 模型预测能力差异明显
- ✅ 追求超额收益
- ✅ 可以承受较高换手率
2.4 权重平滑处理
def ic_weight_smooth(predictions, ic_history, window=20, alpha=1.0):
"""
平滑 IC 权重(Softmax)
参数:
predictions: 股票预测分数
ic_history: IC 历史数据
window: IC 计算窗口
alpha: Softmax 系数
返回:
weights: 股票权重,权重和为1
"""
# 1. 计算滚动 IC
rolling_ic = ic_history.rolling(window=window).mean()
# 2. Softmax 归一化
exp_ic = np.exp(alpha * rolling_ic)
weights = exp_ic / exp_ic.sum()
return weights
# 示例
weights_smooth = ic_weight_smooth(predictions, ic_history, window=5, alpha=1.0)
print("平滑 IC 权重:")
print(weights_smooth)3. 均值-方差优化
3.1 算法步骤
import cvxpy as cp
def mv_optimization(returns, risk_aversion=1.0, max_weight=0.1, min_weight=0.0):
"""
均值-方差优化
参数:
returns: 历史收益率
risk_aversion: 风险厌恶系数
max_weight: 最大单股权重
min_weight: 最小单股权重
返回:
weights: 最优权重
"""
n = len(returns.columns)
# 1. 估计期望收益
mu = returns.mean().values
# 2. 估计协方差矩阵
sigma = returns.cov().values
# 3. 正则化(防止过拟合)
sigma_reg = sigma + 1e-6 * np.eye(n)
# 4. 定义变量
w = cp.Variable(n)
# 5. 定义目标函数
objective = cp.Maximize(mu @ w - risk_aversion * cp.quad_form(w, sigma_reg))
# 6. 定义约束
constraints = [
cp.sum(w) == 1, # 权重和为1
w >= min_weight, # 最小权重
w <= max_weight # 最大权重
]
# 7. 求解
problem = cp.Problem(objective, constraints)
problem.solve()
# 8. 获取解
optimal_weights = pd.Series(w.value, index=returns.columns)
return optimal_weights
# 示例
import numpy as np
# 生成示例数据
np.random.seed(42)
n_stocks = 10
n_days = 252
stocks = [f'stock_{i}' for i in range(n_stocks)]
dates = pd.date_range('2020-01-01', periods=n_days, freq='D')
returns = pd.DataFrame(
np.random.randn(n_days, n_stocks) * 0.02,
index=dates,
columns=stocks
)
# MV 优化
weights_mv = mv_optimization(
returns,
risk_aversion=1.0,
max_weight=0.2,
min_weight=0.0
)
print("MV 优化权重:")
print(weights_mv)3.2 关键参数
risk_aversion(风险厌恶系数)
- 推荐范围:0.1-10.0
- 小值:更注重收益,风险大
- 大值:更注重风险,收益可能低
max_weight(最大单股权重)
- 推荐范围:0.05-0.2
- 限制单股权重,控制风险
lambda_reg(正则化系数)
- 推荐范围:1e-6-1e-3
- 正则化协方差矩阵,防止过拟合
estimation_window(参数估计窗口)
- 推荐范围:126-504(半年到两年)
- 窗口越大,估计越稳定,但可能滞后
3.3 适用场景
- ✅ 追求风险调整后收益
- ✅ 有良好的数据质量
- ✅ 需要科学的资产配置
- ✅ 理解量化投资理论
- ❌ 不适合新手
3.4 风险厌恶系数优化
def optimize_risk_aversion(returns, ra_values=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0]):
"""
优化风险厌恶系数
参数:
returns: 历史收益率
ra_values: 风险厌恶系数候选值
返回:
results: 不同风险厌恶系数的结果
"""
results = []
for ra in ra_values:
# 计算最优权重
weights = mv_optimization(returns, risk_aversion=ra, max_weight=0.2)
# 计算组合收益和风险
portfolio_return = (returns * weights).sum(axis=1).mean()
portfolio_std = (returns * weights).sum(axis=1).std()
sharpe = portfolio_return / portfolio_std
results.append({
'risk_aversion': ra,
'return': portfolio_return,
'std': portfolio_std,
'sharpe': sharpe,
'n_stocks': (weights > 0.01).sum()
})
return pd.DataFrame(results)
# 示例
ra_results = optimize_risk_aversion(returns)
print("风险厌恶系数优化:")
print(ra_results)4. 三种方法对比
4.1 性能对比
def compare_strategies(predictions, returns, k=20, risk_aversion=1.0):
"""
对比三种策略
参数:
predictions: 股票预测分数
returns: 历史收益率
k: Top-K 的 K 值
risk_aversion: 风险厌恶系数
返回:
comparison: 对比结果
"""
# 1. Top-K 等权
weights_topk = top_k_equal_weight(predictions, k=k)
return_topk = (returns * weights_topk).sum(axis=1)
sharpe_topk = return_topk.mean() / return_topk.std()
# 2. MV 优化
weights_mv = mv_optimization(returns, risk_aversion=risk_aversion)
return_mv = (returns * weights_mv).sum(axis=1)
sharpe_mv = return_mv.mean() / return_mv.std()
# 3. 对比结果
comparison = pd.DataFrame({
'Strategy': ['Top-K', 'MV'],
'Mean Return': [return_topk.mean(), return_mv.mean()],
'Std': [return_topk.std(), return_mv.std()],
'Sharpe': [sharpe_topk, sharpe_mv],
'N Stocks': [(weights_topk > 0).sum(), (weights_mv > 0.01).sum()],
'Max Weight': [weights_topk.max(), weights_mv.max()]
})
return comparison
# 示例
comparison = compare_strategies(predictions, returns, k=3, risk_aversion=1.0)
print("策略对比:")
print(comparison)4.2 综合对比表
| 维度 | Top-K 等权 | IC 权重 | MV 优化 |
|---|---|---|---|
| 复杂度 | 低 | 中 | 高 |
| 数据需求 | 低 | 中 | 高 |
| 理论支撑 | 经验 | 统计 | 理论 |
| 计算速度 | 快 | 中 | 慢 |
| 换手率 | 中 | 高 | 中 |
| 风险控制 | 弱 | 中 | 强 |
| 适合新手 | ✅ | ⚠️ | ❌ |
| 适合实战 | ✅ | ✅ | ✅ |
5. 实践建议
5.1 策略选择
新手建议:
- 从 Top-K 等权开始
- 使用较小的 K 值(20-30)
- 定期调仓(周频或月频)
- 严格风险控制
进阶建议:
- 尝试 IC 权重策略
- 学习 MV 优化方法
- 进行参数敏感性分析
- 做样本外验证
专家建议:
- 结合多种方法
- 动态调整策略
- 考虑市场环境
- 持续优化改进
5.2 参数优化建议
1. 避免过拟合
- 使用样本外验证
- 限制参数数量
- 参数有经济学含义
2. 参数稳定性
- 在不同时间段表现稳定
- 对数据变化不过敏
- 定期重新评估参数
3. 实践经验
# ❌ 错误做法
# 过度优化,参数过多
params = {
'k': 23,
'lookback': 17,
'threshold': 0.032,
'smoothing': 0.7,
# ... 太多参数
}
# ✅ 正确做法
# 少量关键参数
params = {
'k': 20, # 清晰的含义
'lookback': 20 # 基于经济逻辑
}5.3 风险控制建议
1. 分散化
- 持有多只股票(至少 10 只)
- 考虑行业分散
- 避免过度集中
2. 仓位控制
def position_sizing(weights, max_position=0.1):
"""
仓位控制
参数:
weights: 原始权重
max_position: 最大单股权重
返回:
controlled_weights: 控制后的权重
"""
# 限制单股权重
controlled_weights = weights.clip(upper=max_position)
# 重新归一化
controlled_weights = controlled_weights / controlled_weights.sum()
return controlled_weights3. 行业中性化
def industry_neutralize(weights, industry_weights, target=0.0):
"""
行业中性化
参数:
weights: 股票权重
industry_weights: 行业权重
target: 目标行业权重
返回:
neutralized_weights: 中性化后的权重
"""
# 计算行业偏离
industry_exposure = (weights * industry_weights).sum()
# 调整权重
neutralized_weights = weights * (target / industry_exposure)
return neutralized_weights总结
三种投资组合构建方法各有优劣:
- Top-K 等权:简单可靠,适合新手
- IC 权重:动态调整,适合有历史数据
- MV 优化:科学配置,适合追求风险调整后收益
建议:
- 新手从 Top-K 等权开始
- 进阶后尝试 IC 权重
- 专家可以使用 MV 优化
- 结合多种方法,取长补短