投资组合构建方法

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 策略选择

新手建议

  1. 从 Top-K 等权开始
  2. 使用较小的 K 值(20-30)
  3. 定期调仓(周频或月频)
  4. 严格风险控制

进阶建议

  1. 尝试 IC 权重策略
  2. 学习 MV 优化方法
  3. 进行参数敏感性分析
  4. 做样本外验证

专家建议

  1. 结合多种方法
  2. 动态调整策略
  3. 考虑市场环境
  4. 持续优化改进

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_weights

3. 行业中性化

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

总结

三种投资组合构建方法各有优劣:

  1. Top-K 等权:简单可靠,适合新手
  2. IC 权重:动态调整,适合有历史数据
  3. MV 优化:科学配置,适合追求风险调整后收益

建议

  • 新手从 Top-K 等权开始
  • 进阶后尝试 IC 权重
  • 专家可以使用 MV 优化
  • 结合多种方法,取长补短