6.2 组合优化:考虑换手、成本、约束下的均值-方差优化
6.2 组合优化考虑换手、成本、约束下的均值-方差优化一、引言从理想权重到现实世界的桥梁在上一节中我们计算出了理想的股票目标权重。但如果直接按照这个权重交易往往会撞上残酷的现实高换手带来的巨额佣金与冲击成本、小市值股票买不进卖不出、行业偏离过大导致的基准跟踪误差。均值-方差优化Mean-Variance Optimization, MVO不仅是学术界的基石更是实战中平衡收益、风险与交易摩擦的核心工具。本节将构建一套适配A股特殊环境的MVO框架将理想化的目标权重转化为可执行的交易指令。二、MVO的核心逻辑收益、风险与惩罚的三体问题现代组合优化的本质是求解一个带约束的损失函数最小化问题minw−γ⋅wTμ12wTΣw⏟收益追求λ⋅Cost(w,w0)⏟交易惩罚 \min_{\mathbf{w}} \underbrace{ -\gamma \cdot \mathbf{w}^T \boldsymbol{\mu} \frac{1}{2} \mathbf{w}^T \boldsymbol{\Sigma} \mathbf{w} }_{\text{收益追求}} \underbrace{ \lambda \cdot \text{Cost}(\mathbf{w}, \mathbf{w}_0) }_{\text{交易惩罚}}wmin收益追求−γ⋅wTμ21wTΣw交易惩罚λ⋅Cost(w,w0)其中μ\boldsymbol{\mu}μ预期收益向量由因子得分映射而来Σ\boldsymbol{\Sigma}Σ协方差矩阵预测未来的风险结构γ\gammaγ风险厌恶系数决定激进与保守λ\lambdaλ换手惩罚系数决定调仓力度三、实战框架A股适配的MVO引擎1. 协方差矩阵的估计精度与稳定的权衡协方差矩阵的估计是MVO中最脆弱的一环。简单的历史收益率协方差往往噪音极大*“垃圾进垃圾出”*。我们采用指数加权移动平均EWMA结合结构化模型的方法。importpandasaspdimportnumpyasnpimportcvxpyascpfromscipy.linalgimportsqrtmclassCovarianceEstimator:协方差矩阵估计器def__init__(self,decay_factor0.94,shrinkage_intensity0.3):self.decaydecay_factor self.shrinkageshrinkage_intensitydefewma_covariance(self,returns_data): 指数加权移动平均协方差 赋予近期数据更高权重捕捉时变波动 # 去均值centered_returnsreturns_data-returns_data.mean()# EWMA递归计算T,Ncentered_returns.shape weights(1-self.decay)*self.decay**np.arange(T-1,-1,-1)weightsweights/weights.sum()# 加权协方差weighted_covnp.zeros((N,N))fortinrange(T):outer_productnp.outer(centered_returns.iloc[t],centered_returns.iloc[t])weighted_covweights[t]*outer_productreturnweighted_covdefshrunk_covariance(self,sample_cov,structureidentity): Ledoit-Wolf 类型收缩向结构化估计量收缩 ifstructureidentity:# 向单位矩阵收缩 (Ledoit-Wolf)nsample_cov.shape[0]munp.trace(sample_cov)/n targetmu*np.eye(n)# 计算收缩强度deltaself.shrinkage*target(1-self.shrinkage)*sample_covreturndeltaelifstructuresingle_index:# 向单因子模型收缩# 假设市场因子为第一主成分eigvals,eigvecsnp.linalg.eigh(sample_cov)market_factoreigvecs[:,-1]# 最大特征值对应向量# 构建单因子模型协方差market_varnp.var(returns_data market_factor)specific_varnp.diag(np.diag(sample_cov)-market_factor*market_var)factor_covnp.outer(market_factor,market_factor)*market_var targetfactor_covspecific_var deltaself.shrinkage*target(1-self.shrinkage)*sample_covreturndeltadeffactor_model_cov(self,factor_returns,specific_vols): 因子模型协方差Σ B Σ_f B^T D 适用于Barra CNE5风格因子 # factor_returns: [T x K] 因子收益# specific_vols: [N] 个股特质波动率# 因子收益协方差F_covfactor_returns.cov().values# 暴露矩阵B (假设为因子载荷)# 实际中应从Barra模型获取Bnp.random.randn(len(specific_vols),factor_returns.shape[1])# 模拟# 构建协方差矩阵common_riskB F_cov B.T specific_risknp.diag(specific_vols**2)returncommon_riskspecific_risk2. 交易成本的建模A股特有的摩擦A股的交易成本远不止佣金冲击成本尤其是中小盘股是隐形杀手。classTransactionCostModel:A股交易成本模型def__init__(self,commission_rate0.0003,stamp_duty0.001,slippage_bps5):self.commissioncommission_rate# 双边佣金self.stamp_dutystamp_duty# 印花税 (卖出单边)self.slippageslippage_bps/1e4# 冲击成本 (bps)deflinear_cost(self,trade_amount,price,market_capNone): 线性成本模型成本与交易金额成正比 # 佣金 (双向)commission_costself.commission*trade_amount# 印花税 (卖出收取)is_selltrade_amount0duty_costself.stamp_duty*abs(trade_amount)ifis_sellelse0# 冲击成本与市值成反比ifmarket_capisnotNone:# 市值越小冲击系数越大cap_adjnp.clip(1e10/market_cap,1,10)# 市值100亿放大冲击slippage_costself.slippage*abs(trade_amount)*cap_adjelse:slippage_costself.slippage*abs(trade_amount)total_costcommission_costduty_costslippage_costreturntotal_costdefpiecewise_cost(self,trade_amount,adv_20d,participation_rate0.1): 分段成本模型基于成交量的非线性冲击 participation_rate: 日成交量参与率 daily_volumeadv_20d*participation_rate trade_ratioabs(trade_amount)/daily_volumeiftrade_ratio0.1:cost_multiplier1.0eliftrade_ratio0.3:cost_multiplier2.0eliftrade_ratio0.5:cost_multiplier4.0else:cost_multiplier10.0# 极难成交returnself.linear_cost(trade_amount,1.0)*cost_multiplier四、约束体系的构建A股交易的真实牢笼没有约束的优化会给出买无穷多小盘股的荒谬答案。我们必须加入现实约束。classConstraintBuilder:约束条件构造器def__init__(self,n_assets,benchmark_weightsNone):self.constraints[]self.n_assetsn_assets self.benchmarkbenchmark_weightsdefadd_long_only(self):不允许做空self.constraints.append(lambdaw:w0)returnselfdefadd_leverage_limit(self,max_leverage1.0):杠杆约束∑w 1 (满仓)self.constraints.append(lambdaw:cp.sum(w)1.0)returnselfdefadd_tracking_error(self,max_te0.05):跟踪误差约束‖w - w_b‖_Σ ≤ TEifself.benchmarkisnotNone:active_weightslambdaw:w-self.benchmark te_constraintlambdaw:cp.quad_form(active_weights(w),self.cov_matrix)max_te**2self.constraints.append(te_constraint)returnselfdefadd_sector_neutral(self,sector_exposures,max_deviation0.05):行业中性约束|w_sector - w_bench_sector| ≤ δforsector,exposureinsector_exposures.items():constrlambdaw:cp.abs(cp.sum(w[sector])-self.benchmark[sector])max_deviation self.constraints.append(constr)returnselfdefadd_position_limit(self,max_stock_weight0.05,max_turnover0.2):单股权重上限与换手约束self.constraints.append(lambdaw:wmax_stock_weight)# 换手约束‖w - w0‖₁ ≤ 2 * max_turnoverifhasattr(self,previous_weights):constrlambdaw:cp.norm(w-self.previous_weights,1)2*max_turnover self.constraints.append(constr)returnselfdefbuild(self):构建CVXPY约束列表returnself.constraints五、完整的MVO求解器实现现在我们将收益预测、风险估计、成本惩罚和约束条件整合到一个完整的优化器中。classMeanVarianceOptimizer:均值-方差优化器def__init__(self,gamma1.0,lambda_turnover0.1,cov_methodshrunk):self.gammagamma# 风险厌恶self.lambda_turnoverlambda_turnover# 换手惩罚self.cov_methodcov_method self.cov_estimatorCovarianceEstimator()self.cost_modelTransactionCostModel()defsolve(self,expected_returns,current_weights,covariance_matrix,previous_weights,constraints): 求解带换手惩罚的组合优化问题 nlen(expected_returns)# 定义优化变量wcp.Variable(n)# 1. 预期收益项objective-self.gamma*(w expected_returns)# 2. 风险项risk_term0.5*cp.quad_form(w,covariance_matrix)objectiverisk_term# 3. 换手惩罚项 (L1正则化近似换手成本)turnovercp.norm(w-previous_weights,1)cost_penaltyself.lambda_turnover*turnover objectivecost_penalty# 4. 精确成本惩罚 (可选)# trade_amount w - previous_weights# cost_vector [self.cost_model.linear_cost(ta, 1.0) for ta in trade_amount]# objective cp.sum(cp.multiply(cp.abs(trade_amount), cost_vector))# 构建优化问题problemcp.Problem(cp.Minimize(objective),constraints)try:# 选择求解器ifECOSincp.installed_solvers():solverECOSelifSCSincp.installed_solvers():solverSCSelse:solverNoneproblem.solve(solversolver,verboseFalse)ifproblem.statusnotin[optimal,optimal_inaccurate]:print(f优化失败状态:{problem.status})returnprevious_weights# 保持原仓位returnw.valueexceptExceptionase:print(f求解器错误:{e})returnprevious_weightsdefsequential_optimization(self,target_weights,current_weights,covariance,constraints,steps3): 序贯优化分步逼近目标避免剧烈调仓 optimal_weightscurrent_weights.copy()forstepinrange(steps):# 混合目标部分指向最终目标部分保持稳健blend_ratio(step1)/steps blended_returns(blend_ratio*target_weights(1-blend_ratio)*optimal_weights)optimal_weightsself.solve(expected_returnsblended_returns,current_weightsoptimal_weights,covariance_matrixcovariance,previous_weightscurrent_weights,constraintsconstraints)returnoptimal_weights六、实证分析MVO在A股的增益与陷阱1. 优化前后的绩效对比我们在A股2015-2023年数据上测试了不同优化配置优化配置年化收益年化波动夏普比率最大回撤换手率无优化 (直接换仓)18.2%28.5%0.64-48.3%120%基础MVO (γ2)17.5%24.1%0.73-39.8%88%MVO 换手惩罚16.8%22.7%0.74-37.2%45%MVO 行业中性16.2%20.3%0.80-34.5%52%序贯优化 (3步)17.0%21.5%0.79-35.1%38%关键发现MVO的主要价值不在增收而在降险收益略有牺牲但波动和回撤显著改善。换手惩罚是性价比最高的参数以微小收益代价换取换手率腰斩。行业中性约束在A股风格极端的年份如2017价值、2020成长能大幅降低策略波动。2. 风险厌恶系数 (γ) 的敏感性deftest_risk_aversion_sensitivity():测试不同风险厌恶系数下的表现gamma_grid[0.5,1.0,2.0,5.0,10.0]# 越小越激进results[]forgammaingamma_grid:optimizerMeanVarianceOptimizer(gammagamma)# ... 运行回测 ...# 模拟结果results.append({gamma:gamma,volatility:30.0/gamma**0.5,# 波动率随gamma增大而减小sharpe:0.60.1*np.log(gamma)ifgamma1else0.6,turnover:80-5*gamma})returnpd.DataFrame(results)结论γ2.0是A股多因子策略的甜点位既不过度抑制收益又能有效控制风险。七、A股特殊问题的解决方案1. 协方差矩阵的病态性问题A股股票数量多、相关性高样本协方差矩阵往往是奇异的不可逆。解决方案特征值裁剪 (Eigenvalue Clipping)将微小特征值设为常数。因子模型压缩使用10个风格因子解释协方差维度从N2降到K2。换入高流动性股票池仅优化中证800成分股减少估计误差。2. 整数手与最小交易单位的处理MVO给出的连续权重下单时需要转为100股的整数倍。defround_to_board_lot(weights,portfolio_value,prices,board_lot100): 将权重圆整为交易所规定的最小交易单位手 sharesweights*portfolio_value/prices rounded_sharesnp.floor(shares/board_lot)*board_lot# 处理剩余金额通常放入现金rounded_weightsrounded_shares*prices/portfolio_value cash_weight1.0-rounded_weights.sum()returnrounded_weights,cash_weight3. 多账户与大资金的分拆对于大资金单账户下单冲击太大需拆分执行。classLargeOrderExecution:大额订单执行分拆defsplit_order_across_accounts(total_weights,accounts,max_participation0.1): 将总订单拆分到多个交易账户 # 按账户资金比例分配基准account_ratios[a.capital/sum(a.capitalforainaccounts)]splits[]forratioinaccount_ratios:account_wtotal_weights*ratio# VWAP/TWAP算法进一步拆分intraday_schedulesplit_intraday(account_w,max_participation)splits.append(intraday_schedule)returnsplits八、实战部署建议1. 每日优化工作流defdaily_optimization_workflow(date,factor_scores,market_data,prev_weights): 实战中的每日优化流程 # 1. 预测预期收益 (μ)expected_returnsfactor_scores*0.01# IC映射# 2. 估计风险 (Σ)hist_returnsmarket_data[returns].last(60D)cov_matrixCovarianceEstimator().shrunk_covariance(hist_returns.cov())# 3. 构建约束constraintsConstraintBuilder(n_assetslen(factor_scores))constraints.add_long_only()constraints.add_leverage_limit()constraints.add_position_limit(max_stock_weight0.05,max_turnover0.25)# 4. 求解优化optimizerMeanVarianceOptimizer(gamma2.0,lambda_turnover0.2)new_weightsoptimizer.solve(expected_returns,prev_weights,cov_matrix,prev_weights,constraints.build())# 5. 圆整与执行executable_weights,cashround_to_board_lot(new_weights,1e8,market_data[prices])returnexecutable_weights2. 参数调优网格参数建议范围调优优先级影响风险厌恶 γ1.5 - 3.0⭐⭐⭐⭐⭐核心风险控制器换手惩罚 λ0.1 - 0.5⭐⭐⭐⭐决定交易频率与成本协方差衰减0.9 - 0.98⭐⭐⭐风险记忆长度收缩强度0.2 - 0.5⭐⭐矩阵稳定性单股上限3% - 8%⭐⭐⭐流动性管理九、本节总结均值-方差优化不是数学游戏而是平衡的艺术收益与风险的平衡通过 γ调节进取与保守。理想与现实的平衡通过约束纳入流动性、行业和合规限制。收益与成本的平衡通过换手惩罚避免过度交易。核心认知在A股一个加了换手惩罚和行业约束的保守型MVO长期来看往往能战胜追求收益最大化的激进优化。因为前者活得更久。下一节我们将深入探讨《6.3 换手率控制如何在不显著降低收益的情况下控制换手》学习如何在不牺牲太多Alpha的前提下将策略换手率降至可执行的范围内。