第 9 章 · 02 Portfolio 组合构建与优化器 ★


文档摘要

第 9 章 · 02 Portfolio 组合构建与优化器 ★ 本节摘要:本节是全书量化含量最高的一节,钻取 Framework 五层最复杂的一层——Portfolio 组合构建。 把 Alpha 层的"方向预测"转成"每只股票具体持仓多少股"。简单实现如 (等权,1/N);复杂实现把 Insight 的预测喂给独立的组合优化器( )求解最优权重——Black-Litterman、Markowitz 均值方差、最大夏普、最小方差、风险平价、UnconstrainedMeanVariance 等经典量化模型齐备,优化器内部用 Accord.NET / MathNet.Numerics 做二次规划求解。

第 9 章 · 02 Portfolio 组合构建与优化器 ★

本节摘要:本节是全书量化含量最高的一节,钻取 Framework 五层最复杂的一层——Portfolio 组合构建。IPortfolioConstructionModel.CreateTargets(QCAlgorithm, Insight[])→IEnumerable<IPortfolioTarget> 把 Alpha 层的"方向预测"转成"每只股票具体持仓多少股"。简单实现如 EqualWeightingPortfolioConstructionModel(等权,1/N);复杂实现把 Insight 的预测喂给独立的组合优化器(IPortfolioOptimizer)求解最优权重——Black-Litterman、Markowitz 均值方差、最大夏普、最小方差、风险平价、UnconstrainedMeanVariance 等经典量化模型齐备,优化器内部用 Accord.NET / MathNet.Numerics 做二次规划求解。本节还会讲 Markowitz 的收益-风险权衡有效前沿理论,以及 PortfolioOptimizerPythonWrapper 怎么让用户用 Python 写优化器接入 C# 引擎。读完本节,你将真正理解 Lean 为什么被称为"专业级量化引擎"。

内容来源:原项目源码 Algorithm.Framework/Portfolio/(11 个组合模型 + 4 个优化器 + Python 包装)、Algorithm/Portfolio/IPortfolioConstructionModel.cs,精读并套用体系化模板。

⚠️ 注意:本节涉及量化金融的核心数学(协方差、二次规划、有效前沿)。读者需有"组合优化是优化问题:目标函数 + 约束 + 求解器"的基本认识。代码里大量使用 Accord.Math 的矩阵/约束 API,看不懂 Accord 没关系,关注目标函数和约束即可。

学习目标

阅读完本节,你应当能够:

  1. 解释 IPortfolioConstructionModel.CreateTargets 怎么把 Insight[] 转成 IPortfolioTarget[]
  2. 区分"组合构建模型"(IPortfolioConstructionModel)与"组合优化器"(IPortfolioOptimizer)两个抽象层级。
  3. 说清 EqualWeighting / InsightWeighting / ConfidenceWeighted / AccumulativeInsight 四种简单权重模型的差别。
  4. 读懂 Markowitz 均值方差理论的目标函数与有效前沿概念。
  5. 列举四种内置优化器(最大夏普/最小方差/风险平价/无约束均值方差)的优化目标。
  6. 理解 PortfolioOptimizerPythonWrapper 怎么让 Python 优化器接入。

一、IPortfolioConstructionModel:从信号到目标持仓

Algorithm/Portfolio/IPortfolioConstructionModel.cs:24-33:

24 public interface IPortfolioConstructionModel : INotifiedSecurityChanges 25 { 32 IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights); 33 }

输入是 Alpha 层产出的 Insight[](见上一节),输出是 IPortfolioTarget[]IPortfolioTarget 极简——只有 Symbol 和 Quantity(目标持仓股数):

public interface IPortfolioTarget { Symbol Symbol { get; } decimal Quantity { get; } }

Portfolio 层的职责:把"方向预测"(Insight.Direction)翻译成"具体几股"(Quantity)。这一步的策略空间极大——可以是简单的"有 N 个 Up 信号就每只买总资金的 1/N",也可以是"用协方差矩阵求最大夏普权重"。

二、两层抽象:组合模型 + 优化器

Lean 把 Portfolio 层拆成两个抽象:

  • IPortfolioConstructionModel(组合构建模型):决定"什么时候调仓、把哪些 Insight 送进优化器、把优化结果乘上总资金转成 Quantity"。这一层负责"框架"。
  • IPortfolioOptimizer(组合优化器):决定"给定历史收益矩阵和预期收益,怎么算最优权重向量"。这一层负责"数学"。

IPortfolioOptimizer 的接口极简:

public interface IPortfolioOptimizer { double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null); }

输入是历史收益矩阵(K 期 × N 资产)、预期收益向量(N)、协方差矩阵(N × N),输出是权重向量(N)。优化器不关心 Symbol、不关心 Insight,只关心纯数学的"给我收益数据,返回权重"。这种数学与业务的彻底分离是 Lean 设计的精妙——研究员可以独立替换优化器,不影响策略框架。

三、简单权重模型:不需要优化器

四个简单模型用启发式权重,不调用优化器:

3.1 EqualWeightingPortfolioConstructionModel(等权,最简)

EqualWeightingPortfolioConstructionModel.cs:118-132 核心方法 DetermineTargetPercent:

118 protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights) 119 { 120 var result = new Dictionary<Insight, double>(activeInsights.Count); 123 var count = activeInsights.Count(x => x.Direction != InsightDirection.Flat && RespectPortfolioBias(x)); 124 var percent = count == 0 ? 0 : 1m / count; // 每只 1/N 125 foreach (var insight in activeInsights) 126 { 127 result[insight] = 128 (double)((int)(RespectPortfolioBias(insight) ? insight.Direction : InsightDirection.Flat) 129 * percent); // 方向 × 权重(空信号 -1/N) 130 } 131 return result; 132 }

逻辑极简:有 N 个非 Flat 信号就每只分配 1/N,Down 信号取负(insight.Direction 是 -1,乘出来是 -1/N)。基类 PortfolioConstructionModel 把百分比乘以总资金转成 IPortfolioTarget 的 Quantity,然后 CreateTargets 输出。

RespectPortfolioBias 处理多空偏好(PortfolioBias.LongShort / Long / Short)——Long 模式下 Down 信号会被改成 Flat。

3.2 其他简单模型

模型 权重来源
InsightWeightingPortfolioConstructionModel 直接用 Insight.Weight 字段
ConfidenceWeightedPortfolioConstructionModel Insight.Confidence 归一化加权
AccumulativeInsightPortfolioConstructionModel 同一标的多个未过期 Insight 累积计算
MeanReversionPortfolioConstructionModel 偏离均值的反转加权
SectorWeightingPortfolioConstructionModel 按行业先均分再行业内等权

这些都不需要数学优化器,只做"信号 → 权重"的简单映射。

四、优化器驱动的组合模型

BlackLittermanOptimizationPortfolioConstructionModel / MeanVarianceOptimizationPortfolioConstructionModel / MaximumSharpeRatioPortfolioConstructionModel / RiskParityPortfolioConstructionModel 这四个组合模型都内部持有一个 IPortfolioOptimizer 实例。组合模型负责:

  1. 用历史数据(algorithm.History)算每只标的的收益矩阵和协方差矩阵。
  2. 把 Alpha 层的 Insight 转成预期收益向量(expectedReturns)。
  3. 调用 optimizer.Optimize(历史收益, 预期收益, 协方差) 得到权重向量。
  4. 把权重乘总资金,转成 IPortfolioTarget[] 输出。

五、Markowitz 均值方差:理论回顾

理解优化器需要先回顾 1952 年马科维茨的均值方差理论。核心命题:给定 N 个资产的预期收益向量 μ 和协方差矩阵 Σ,求权重向量 w,使得组合在给定预期收益下风险最小(或等价地,在给定风险下收益最大)。

组合的预期收益:E[R] = μᵀw

组合的方差(风险):σ² = wᵀΣw

有效前沿:在(风险, 收益)平面上,所有"在某个风险水平下收益最大"的组合连成的曲线。前沿上的组合都是"最优"的——再找不到同风险更高收益或同收益更低风险的组合。

四个优化器对应不同目标:

优化器 优化目标 数学形式
MinimumVariancePortfolioOptimizer 全局最小方差 min wᵀΣw s.t. 1ᵀw=1
UnconstrainedMeanVarianceOptimizer 无约束均值方差 min wᵀΣw - λμᵀw
MaximumSharpeRatioPortfolioOptimizer 最大夏普比率 max (μ-r_f)ᵀw / √(wᵀΣw)
RiskParityPortfolioOptimizer 风险平价(各资产风险贡献相等) 各资产边际风险贡献相等

六、MaximumSharpeRatioPortfolioOptimizer 精读

最大夏普优化器是数学最复杂的一个。MaximumSharpeRatioPortfolioOptimizer.cs:89-138Optimize 方法:

89 public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null) 90 { 91 covariance = covariance ?? historicalReturns.Covariance(); 92 var returns = (expectedReturns ?? historicalReturns.Mean(0)).Subtract(_riskFreeRate); 94 var size = covariance.GetLength(0); 95 var equalWeights = Vector.Create(size, 1.0 / size); 99 var feasible = _lower >= 0 ? returns.Any(x => x > 0) : returns.Any(x => x != 0); 100 if (!feasible) return equalWeights; // 不可行退化为等权 105 // Charnes-Cooper 替换 y = κw (κ = 1ᵀy):最大化夏普 106 // (µ − r_f)ᵀw / √(wᵀΣw) 转化为 min yᵀΣy s.t. (µ − r_f)ᵀy = 1 109 var constraints = new List<LinearConstraint> 110 { 111 new LinearConstraint(size) 112 { CombinedAs = returns, ShouldBe = ConstraintType.EqualTo, Value = 1.0 } 116 }; 121 constraints.AddRange(GetBoundaryConditions(size)); // lw ≤ w ≤ up 124 var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0)); 125 var solver = new GoldfarbIdnani(optfunc, constraints); 128 var success = solver.Minimize(Vector.Copy(equalWeights)); 129 if (!success) return equalWeights; 135 var y = solver.Solution; 136 var sum = y.Sum(); 137 return sum > 0 ? y.Divide(sum) : equalWeights; // w = y / (1ᵀy) 恢复权重 138 }

6.1 Charnes-Cooper 替换(精彩技巧)

夏普比率 S = (μ-r_f)ᵀw / √(wᵀΣw)分式规划,直接优化难。L105-106 的 Charnes-Cooper 替换是经典技巧:令 y = κw(其中 κ = 1/(1ᵀy) > 0),把分式目标转成二次目标:

max (μ-r_f)ᵀw / √(wᵀΣw) ⟹ min yᵀΣy s.t. (μ-r_f)ᵀy = 1, lw ≤ w= y/(1ᵀy) ≤ up

求解后用 w = y / (1ᵀy) 恢复权重(L134-137)。这是把"最大夏普"包装成二次规划求解器的标准做法。

6.2 GoldfarbIdnani 求解器

L124-128 用 Accord.NET 的 GoldfarbIdnani 算法求解带约束的二次规划——目标函数是二次(yᵀΣy),约束是线性((μ-r_f)ᵀy = 1、上下界)。Goldfarb-Idnani 是经典的对偶 active-set 方法,适合中小规模(N 通常几十到几百)的 QP。

💡 钻取要点:三个细节体现工程素养:(1) L99-103 不可行退化为等权——如果所有预期收益都 ≤ 0(纯空头市场),夏普无解,优雅退化;(2) L129 求解失败退化等权——数值算法可能不收敛,必须有兜底;(3) L137 sum > 0 ? ... : equalWeights——1ᵀy ≤ 0 时恢复无意义,也退化。所有数学分支都有退化路径,这是量化代码的基本要求——任何数值异常都不能崩

七、MinimumVariance 与 RiskParity

MinimumVariancePortfolioOptimizer 目标最简单——min wᵀΣw s.t. 1ᵀw=1, lw≤w≤up,直接用二次规划求组合方差最小化,不需要预期收益。

RiskParityPortfolioOptimizer 实现风险平价——每个资产对组合总风险的边际贡献相等。这意味着低波动资产配更高权重、高波动资产配更低权重,避免被少数高波动资产主导。风险平价桥水基金(Bridgewater)的全天候策略就是这套思路。

UnconstrainedMeanVarianceOptimizer 是经典 Markowitz 无约束版,目标 min wᵀΣw - λμᵀw,λ 是风险厌恶系数。

八、PortfolioOptimizerPythonWrapper:Python 接入

Lean 通过 Python.NET 支持 Python 写优化器。PortfolioOptimizerPythonWrapper.cs 把一个 Python 对象(实现 optimize(historical_returns, expected_returns=None, covariance=None)→numpy.array 方法)包装成 C# 的 IPortfolioOptimizer。用户可以这样:

# 自定义 Python 优化器 class MyOptimizer: def optimize(self, historical_returns, expected_returns=None, covariance=None): # 自定义权重计算,如等风险贡献、机器学习预测组合 ... return weights # numpy array # 在算法里接入 self.SetPortfolioConstruction( MeanVarianceOptimizationPortfolioConstructionModel( portfolioOptimizer=MyOptimizer() ) )

这种 C# 接口 + Python 实现 的桥接,与第 4 章的 QCAlgorithm.Python.cs、第 7 章的 Python 指标是同一套机制——Python.NET 互操作。研究员可以在熟悉的 Python 生态(numpy/scipy/cvxpy)里实现优化算法,无缝接入 C# 引擎。

九、装配:QCAlgorithm.SetPortfolioConstruction

QCAlgorithm.Framework.cs 提供装配入口:

public void SetPortfolioConstruction(IPortfolioConstructionModel portfolioConstruction) { PortfolioConstruction = portfolioConstruction; }

策略里典型用法:

public override void Initialize() { SetAlpha(new EmaCrossAlphaModel(12, 26)); // 信号 SetPortfolioConstruction(new BlackLittermanOptimizationPortfolioConstructionModel()); // 优化器组合 }

换优化器只改一行——从等权到 Black-Litterman,从最大夏普到风险平价,组合行为完全不同,但信号(Alpha)和执行(Execution)代码零改动。这就是"可组装策略工程化"的精髓。

本节要点回顾

  1. Portfolio 层职责:IPortfolioConstructionModel.CreateTargets(insights)→IPortfolioTarget[],把方向预测转成具体持仓股数。
  2. 两层抽象:组合构建模型(框架:何时调仓/转 Quantity)+ 组合优化器(数学:给收益数据返回权重)。IPortfolioOptimizer.Optimize(历史收益, 预期收益, 协方差)→权重
  3. 简单模型(不需优化器):EqualWeighting 1/N、InsightWeighting 用 Weight 字段、ConfidenceWeighted 按置信度、AccumulativeInsight 累积、SectorWeighting 行业均分。
  4. 优化器驱动模型:BlackLitterman / MeanVariance / MaximumSharpeRatio / RiskParity,内部持有 IPortfolioOptimizer,用历史数据算协方差,把 Insight 转预期收益。
  5. Markowitz 均值方差:组合收益 μᵀw、风险 wᵀΣw,有效前沿是"同风险收益最大"组合的曲线。
  6. 四个内置优化器:MinimumVariance(全局最小方差)、UnconstrainedMeanVariance(经典 Markowitz)、MaximumSharpeRatio(最大夏普,Charnes-Cooper 转 QP)、RiskParity(风险平价)。
  7. MaximumSharpeRatio 精读:Charnes-Cooper 替换把分式目标转二次规划,GoldfarbIdnani 求解,三处退化路径(不可行/不收敛/恢复异常)都退化等权。
  8. PortfolioOptimizerPythonWrapper:Python 实现 optimize(...) 方法即可接入,Python.NET 桥接,cvxpy/scipy 生态可用。

下一节钻取 Framework 最后两层——Execution(执行,把目标持仓拆单成真实订单:VWAP/标准差/价差驱动)与 Risk(风控过滤:最大回撤/移动止损/行业暴露),并完成 BasicTemplateFrameworkAlgorithm 五件套演示。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U