第 4 章 · 04 efficient_risk 与 efficient_return


文档摘要

第 4 章 · 04 efficientrisk 与 efficientreturn 本节摘要:本节把两个对称的方法一起讲—— 与 。它们是马科维茨 1952 原论文的核心:前者「在目标波动率下最大化收益」,后者「在目标收益下最小化风险」,从两个方向逼近同一条有效前沿。扫一遍不同的目标值,每个都解出一个最优点,把这些点连起来就是前沿曲线本身。本节拆解两者的源码: 把方差约束写成 ,而 把收益约束写成 ;两者都支持 市场中性模式;还讲清它们各自的边界检查(全局最小波动率、最大可能收益)以及作者关于「不合理目标会静默失败」的警告。 内容来源:原项目源码 、 、文档 、 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 区分 与 的目标与约束互换关系。 用两者描出整条有效前沿。

第 4 章 · 04 efficient_risk 与 efficient_return

本节摘要:本节把两个对称的方法一起讲——efficient_riskefficient_return。它们是马科维茨 1952 原论文的核心:前者「在目标波动率下最大化收益」,后者「在目标收益下最小化风险」,从两个方向逼近同一条有效前沿。扫一遍不同的目标值,每个都解出一个最优点,把这些点连起来就是前沿曲线本身。本节拆解两者的源码:efficient_risk 把方差约束写成 variance <= target_variance,而 efficient_return 把收益约束写成 ret >= target_return_par;两者都支持 market_neutral 市场中性模式;还讲清它们各自的边界检查(全局最小波动率、最大可能收益)以及作者关于「不合理目标会静默失败」的警告。

内容来源:原项目源码 pypfopt/efficient_frontier/efficient_frontier.pypypfopt/objective_functions.py、文档 docs/MeanVariance.rstdocs/UserGuide.rst,汉化并套用体系化模板。

学习目标

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

  1. 区分 efficient_riskefficient_return目标与约束互换关系。
  2. 用两者描出整条有效前沿
  3. 知道两者的边界检查(全局最小波动率、最大可能收益)。
  4. market_neutral=True 求市场中性组合。
  5. 注意「不合理目标静默失败」的陷阱。

一、两个方向逼近同一条前沿

回顾有效前沿的定义:所有满足「给定风险下收益最高」或「给定收益下风险最低」的组合的集合。这两个定义等价——它们描出同一条曲线。efficient_riskefficient_return 就是从这两个方向求解:

方法 目标 约束 直觉
efficient_risk(target_volatility) 最大化收益 波动率 ≤ 目标 「我能承受 X% 波动,求最大收益」
efficient_return(target_return) 最小化方差 收益 ≥ 目标 「我要至少 X% 收益,求最稳」

扫不同的目标值,每个解出一个前沿上的点,连起来就是前沿:

for target in [0.15, 0.18, 0.21, 0.24, 0.27]: ef = EfficientFrontier(mu, S) ef.efficient_risk(target_volatility=target) # 记录 (volatility, return)

二、efficient_risk 源码解析

打开 efficient_frontier.py,定位 efficient_risk(源码 359-416 行)。核心片段:

def efficient_risk(self, target_volatility, market_neutral=False): if not isinstance(target_volatility, (float, int)) or target_volatility < 0: raise ValueError("target_volatility should be a positive float") global_min_volatility = np.sqrt(1 / np.sum(np.linalg.pinv(self.cov_matrix))) if target_volatility < global_min_volatility: raise ValueError( "The minimum volatility is {:.3f}. Please use a higher target_volatility".format( global_min_volatility ) ) # 目标:最大化收益(即最小化负收益) self._objective = objective_functions.portfolio_return(self._w, self.expected_returns) variance = objective_functions.portfolio_variance(self._w, self.cov_matrix) for obj in self._additional_objectives: self._objective += obj # 约束:方差 ≤ 目标方差 target_variance = cp.Parameter( name="target_variance", value=target_volatility**2, nonneg=True ) self.add_constraint(lambda _: variance <= target_variance) self._make_weight_sum_constraint(market_neutral) return self._solve_cvxpy_opt_problem()

关键点:

  1. 边界检查 1:target_volatility 必须正数。
  2. 边界检查 2(关键):目标波动不能低于全局最小波动率。全局最小波动率用 np.sqrt(1/np.sum(np.linalg.pinv(Σ))) 计算——这是 Σ 的伪逆的元素和的倒数的平方根,对应 GMV 组合的波动率。低于它无解。
  3. 目标:portfolio_return(默认 negative=True,即最小化负收益 = 最大化收益)。
  4. 约束:把目标方差做成 cvxpy 的 Parameter(target_volatility**2),加 variance <= target_variance 约束。
  5. 权重和约束:委托 _make_weight_sum_constraint(market_neutral)——非市场中性时 sum(w)==1,市场中性时 sum(w)==0

💡 为何用 Parameter:把 target_variance 包成 cp.Parameter 而非常数,是为了支持后续 update_parameter_value 重解(不改约束结构只改值)。这让「扫不同目标」更高效——但需要配合 is_parameter_defined 的逻辑,且改 market_neutral 仍需重建实例。

三、efficient_return 源码解析

定位 efficient_return(源码 418-470 行)。核心片段:

def efficient_return(self, target_return, market_neutral=False): if not isinstance(target_return, float): raise ValueError("target_return should be a float") if not self._max_return_value: a = self.deepcopy() self._max_return_value = a._max_return() if target_return > self._max_return_value: raise ValueError( "target_return must be lower than the maximum possible return" ) # 目标:最小化方差 self._objective = objective_functions.portfolio_variance(self._w, self.cov_matrix) ret = objective_functions.portfolio_return(self._w, self.expected_returns, negative=False) for obj in self._additional_objectives: self._objective += obj # 约束:收益 ≥ 目标收益 target_return_par = cp.Parameter(name="target_return", value=target_return) self.add_constraint(lambda _: ret >= target_return_par) self._make_weight_sum_constraint(market_neutral) return self._solve_cvxpy_opt_problem()

关键点:

  1. 类型检查:target_return 必须是 float(不接受 int——源码故意如此,提示用户传浮点)。
  2. 边界检查(关键):目标收益不能高于最大可能收益。最大可能收益用 _max_return() 内部方法计算——它求解一个「只最大化收益、不加风险约束」的子问题,得到可行域内的最高收益。
  3. 目标:portfolio_variance(最小化方差)。
  4. 约束:把目标收益做成 Parameter,加 ret >= target_return_par 约束。注意这里 negative=False(取正收益)。
  5. deepcopy:计算 _max_return_value 时用 self.deepcopy() 避免污染当前实例的求解状态——因为 _max_return 内部会调用 _solve_cvxpy_opt_problem,改了 objective。

四、市场中性模式

两者都支持 market_neutral=True:

ef.efficient_return(target_return=0.2, market_neutral=True) ef.efficient_risk(target_volatility=0.2, market_neutral=True)

源码 _make_weight_sum_constraint(152-193 行)的逻辑:

def _make_weight_sum_constraint(self, is_market_neutral): if is_market_neutral: portfolio_possible = np.any(self._lower_bounds < 0) if not portfolio_possible: warnings.warn( "Market neutrality requires shorting - bounds have been amended", RuntimeWarning, ) self._map_bounds_to_constraints((-1, 1)) ... self.add_constraint(lambda w: cp.sum(w) == 0) # 权重和为 0 else: self.add_constraint(lambda w: cp.sum(w) == 1) # 权重和为 1

市场中性要求权重和为 0(多空抵消,组合 beta 趋零),这必然需要负权重(做空)。若你的 weight_bounds 不允许负值,会自动改成 (-1, 1) 并发警告。

⚠️ 为何 max_sharpe 与 min_volatility 不支持市场中性:文档明确——市场中性(权重和为 0)对杠杆不变性有要求,而 max_sharpe(切线组合)与 min_volatility(GMV)对杠杆不满足不变性。只有 efficient_riskefficient_returnmax_quadratic_utility 三者支持。

五、用法示例

5.1 efficient_risk

ef = EfficientFrontier(mu, S) ef.efficient_risk(target_volatility=0.20) # 目标年化波动 20% weights = ef.clean_weights() ef.portfolio_performance(verbose=True)

输出可能:

Expected annual return: 28.5% Annual volatility: 20.0% # 等于目标(约束生效) Sharpe Ratio: 1.42

5.2 efficient_return

ef = EfficientFrontier(mu, S) ef.efficient_return(target_return=0.25) # 目标年化收益 25% ef.portfolio_performance(verbose=True)

输出可能:

Expected annual return: 25.0% Annual volatility: 19.2% Sharpe Ratio: 1.30

5.3 市场中性

ef = EfficientFrontier(mu, S, weight_bounds=(-1, 1)) ef.efficient_return(target_return=0.2, market_neutral=True) for name, value in ef.clean_weights().items(): print(f"{name}: {value:.4f}")

权重会有正有负,且和为 0。

5.4 描前沿

targets = [0.15, 0.17, 0.19, 0.21, 0.23, 0.25] frontier = [] for tv in targets: ef = EfficientFrontier(mu, S) ef.efficient_risk(target_volatility=tv) ret, vol, _ = ef.portfolio_performance() frontier.append((vol, ret)) # 画 (vol, ret) 散点 + 连线即有效前沿

六、参数速查

参数 efficient_risk efficient_return
必填 target_volatility (float > 0) target_return (float)
可选 market_neutral=False market_neutral=False
上界检查 不能低于 global_min_volatility 不能高于 _max_return_value
类型陷阱 int 也接受(自动转 float) 必须 float,int 抛错

⚠️ target_return 必须 float:源码 if not isinstance(target_return, float): raise ValueError。传 0.2 没问题,传 1(int)会报错。这是刻意设计——提示用户「收益是连续浮点」。

七、不合理目标的静默失败

docs/MeanVariance.rst 有一段刺眼的 caution:

若你给 efficient_riskefficient_return 传入不合理的目标,优化器会静默失败并返回怪异权重。Caveat emptor(买者自负)!

虽然源码有边界检查(全局最小波动率、最大可能收益),但数值上接近边界时仍可能出问题:

  • 目标波动刚好等于 GMV 波动:约束太紧,数值不稳定。
  • 目标收益接近最大可能收益:解退化(权重极端集中)。

💡 实务建议:目标值要离边界留有余量。先用 min_volatility() 查 GMV 波动率,再设 efficient_risk 目标比它高 2~5 个百分点;先用 _max_return 或试算查最大收益,再设 efficient_return 目标比它低几个百分点。

本节要点回顾

  1. 对称关系:efficient_risk 最大化收益受波动约束,efficient_return 最小化方差受收益约束——两方向逼近同一前沿。
  2. 边界检查:risk 不能低于 GMV 波动率;return 不能高于最大可能收益(用 _max_return 内部算)。
  3. 市场中性:两者都支持 market_neutral=True(权重和为 0,需负权重);max_sharpe 与 min_volatility 不支持。
  4. 描前沿:扫不同目标值,记录 (vol, ret),连线即有效前沿。
  5. target_return 必须 float:int 报错(刻意设计)。
  6. 静默失败警告:目标数值上接近边界会得到怪异权重——留余量、先试算边界。

下一节是本章最后一个目标——max_quadratic_utility,用风险厌恶系数 δ 平衡收益与方差。


发布者: 作者: 灏天文库 转发
评论区 (0)
U