第九章:期权组合优化目标——夏普比率、索提诺比率、最大回撤约束

做期权组合优化,说白了就是回答一个问题:什么样的组合才算「好」?

我早年刚接触量化时,觉得收益高就是好。后来被市场狠狠教育了几次,才明白——收益必须和风险放在一起看。今天我们就来聊聊三个最核心的优化目标:夏普比率、索提诺比率,以及最大回撤约束。

9.1 夏普比率:最经典的「收益/风险」标尺

夏普比率是业界最通用的指标。它的公式很简单:

Sharpe Ratio = (Rp - Rf) / σp

其中 Rp 是组合预期收益,Rf 是无风险利率,σp 是组合收益的波动率。

核心思想:每承担一单位总风险,能换来多少超额收益。

我在项目中遇到过一件事:有个同事拿了一个夏普比率高达 2.5 的策略给我看,我一看回测曲线——确实漂亮。但仔细检查发现,他用了日频数据,而且没有考虑交易成本。换成周频数据后,夏普直接掉到 0.8。嗯,这里要注意:数据频率和计算方式对夏普比率影响极大

实战要点:
  • 年化夏普比率通常用日收益率计算,再乘以 sqrt(252)
  • 无风险利率建议用 10 年期国债收益率,别用短期利率
  • 期权组合的收益分布往往偏斜,夏普比率会低估尾部风险

9.2 索提诺比率:只惩罚「坏」波动

夏普比率有个天然缺陷:它把上行波动和下行波动一视同仁。但作为期权交易者,你想想看——上涨的波动我们巴不得多来点,真正怕的是下跌

索提诺比率就是来解决这个问题的:

Sortino Ratio = (Rp - Rf) / σd

σd 是下行标准差,只计算收益率低于目标值(通常设为 0 或无风险利率)的部分。

我个人习惯在期权组合优化中优先使用索提诺比率。为什么?因为期权策略天然具有非对称收益特征。比如卖出宽跨式,夏普可能很好看,但一旦遇到黑天鹅,下行风险极大。索提诺比率能更真实地反映这种风险。

我的经验: 如果你在做波动率交易(比如做空波动率),索提诺比率比夏普比率重要得多。我曾经优化一个做空 VIX 的组合,夏普比率 1.8,索提诺只有 0.6——这其实是个危险的信号。

9.3 最大回撤约束:守住底线

夏普和索提诺都是「比率型」指标,它们能告诉你每单位风险换多少收益,但它们不会告诉你组合可能亏多少钱

最大回撤(Max Drawdown)就是用来补这个短板的。它衡量的是:从历史高点跌到最低点,最大亏损幅度是多少。

在期权组合优化中,我通常这样设置约束:

约束条件:
  - 最大回撤 ≤ 15%
  - 回撤恢复期 ≤ 60 个交易日
  - 单日最大亏损 ≤ 5%

为什么要有恢复期约束?因为期权组合如果回撤太大,gamma 和 vega 暴露会急剧变化,恢复起来比股票组合难得多。我曾经见过一个组合回撤 20%,虽然最终涨回来了,但中间因为保证金不足被迫平仓——这就是回撤约束没设好的代价。

避坑指南: 最大回撤是路径依赖的。优化时不要只看最终值,要看回撤曲线。我建议在优化目标中加入「回撤惩罚项」,比如:
目标函数 = 索提诺比率 - λ × (当前回撤 / 最大允许回撤)
这样组合会自动避开那些「高收益但回撤深」的方案。

9.4 三者如何协同优化?

在实际项目中,我不会只用单一指标。我的做法是:

  1. 第一层筛选:用夏普比率过滤掉明显不行的组合(比如夏普 < 0.5)
  2. 第二层优化:以索提诺比率为目标函数,最大化每单位下行风险的收益
  3. 第三层约束:加入最大回撤和恢复期约束,确保组合不会「暴毙」

下面这张图展示了三者的关系:

期权组合优化目标体系 夏普比率 收益 / 总波动 衡量单位总风险收益 适合初步筛选 索提诺比率 收益 / 下行波动 只惩罚坏波动 适合期权策略 最大回撤约束 回撤幅度 + 恢复期 守住风险底线 防止爆仓风险 三层协同优化流程 第一层:夏普筛选 第二层:索提诺优化 第三层:回撤约束 三者缺一不可,共同构成期权组合优化的完整目标体系

9.5 代码实现示例

下面是一个简单的 Python 实现,展示如何在优化中同时考虑这三个目标:

import numpy as np
from scipy.optimize import minimize

def sharpe_ratio(returns, rf=0.03):
    excess = returns.mean() * 252 - rf
    vol = returns.std() * np.sqrt(252)
    return excess / vol if vol > 0 else 0

def sortino_ratio(returns, rf=0.03, target=0):
    excess = returns.mean() * 252 - rf
    downside = returns[returns < target].std() * np.sqrt(252)
    return excess / downside if downside > 0 else 0

def max_drawdown(equity_curve):
    peak = np.maximum.accumulate(equity_curve)
    drawdown = (equity_curve - peak) / peak
    return drawdown.min()

def objective(weights, returns):
    # 组合收益
    port_returns = returns @ weights
    # 索提诺比率(最大化)
    sr = sortino_ratio(port_returns)
    # 回撤惩罚
    equity = (1 + port_returns).cumprod()
    dd = max_drawdown(equity)
    penalty = 0 if dd > -0.15 else 1000  # 回撤超过15%则重罚
    return -sr + penalty

# 假设 returns 是历史收益率矩阵(n_days × n_assets)
# weights 是资产权重向量
result = minimize(objective, 
                  x0=np.ones(5)/5,
                  args=(returns,),
                  constraints=[{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}],
                  bounds=[(0, 0.3)]*5)
个人建议: 代码中的回撤惩罚系数需要根据你的风险偏好调整。我一般先用历史数据跑一遍,看惩罚项对优化结果的影响,再决定具体数值。别一上来就设得太狠,否则优化器会给出过于保守的方案。

9.6 实际应用中的取舍

最后说点实在的。这三个目标不可能同时达到最优——这是优化问题的本质

我做过一个实盘案例:一个跨式期权组合,夏普比率 1.2,索提诺 0.9,最大回撤 12%。我试图把索提诺优化到 1.2 以上,结果回撤飙到了 18%。后来我妥协了,把索提诺目标设在 1.0,回撤控制在 14% 以内——这个方案最终实盘表现反而最好。

所以我的建议是:先定回撤底线,再优化索提诺,最后用夏普做验证。这个顺序我用了很多年,没出过大问题。


公众号:蓝海资料掘金营,微信deep3321