25、多目标优化:帕累托前沿在收益-风险-成本中的权衡

做期权交易的朋友,经常会问我一个问题:

“有没有一个策略,收益最高、风险最低、成本还最小?”

我的回答很直接——没有

这就像你想买一辆车,又要跑得快、又要省油、又要便宜。现实吗?不现实。期权组合优化也是这个道理。收益、风险、成本,这三者天然就是互相拉扯的。你追求高收益,就得承受高风险;你想降低风险,可能就得牺牲收益或者增加成本。

那怎么办?我们得学会在三个目标之间找到“最优的妥协”。这就是今天要聊的——帕累托前沿

什么是帕累托前沿?

先讲个我自己的经历。几年前我在做一套波动率曲面套利系统,目标有三个:最大化年化收益、最小化最大回撤、最小化交易成本。我一开始傻乎乎地写了个单目标优化函数,把三个目标加权求和。结果呢?调参调了三天,出来的组合要么收益高但回撤吓人,要么成本低但收益还不如买理财。

后来我才意识到,这三个目标根本不能简单加权。因为它们之间是“非支配”的关系——你没法在不牺牲一个目标的前提下,同时改善另外两个。

帕累托前沿,说白了就是:一组“不差”的解的集合。在这个集合里,任何一个解,你都没办法在不损害其他目标的情况下,让某个目标变得更好。

核心定义:

如果一个解 A 在所有目标上都优于或等于解 B,且至少在一个目标上严格优于 B,那么 A 就“支配”了 B。所有不被任何其他解支配的解,就构成了帕累托前沿。

举个例子。假设我们有两个策略:

  • 策略A:年化收益20%,最大回撤15%,年化成本2%
  • 策略B:年化收益18%,最大回撤10%,年化成本1.5%

哪个好?没法直接说。A收益高但回撤大、成本高;B收益低但更稳、更便宜。它们互不支配,都在帕累托前沿上。

收益-风险-成本的三维权衡

在期权组合优化里,我们通常面对三个维度:

目标 衡量指标 优化方向
收益 年化收益率、夏普比率 最大化
风险 最大回撤、VaR、波动率 最小化
成本 买卖价差、佣金、滑点 最小化

你想想看,这三个目标放在一起,就是一个三维空间。每个策略对应空间里的一个点。帕累托前沿就是这些点构成的“最优曲面”。

我个人习惯用NSGA-II算法来做多目标优化。为什么?因为它快,而且能很好地保持解的多样性。下面是我常用的代码框架:

import numpy as np
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.core.problem import ElementwiseProblem
from pymoo.optimize import minimize

class OptionPortfolioProblem(ElementwiseProblem):
    def __init__(self):
        super().__init__(
            n_var=10,  # 假设有10个期权头寸
            n_obj=3,   # 三个目标:收益、风险、成本
            xl=0,      # 头寸权重下限
            xu=1       # 头寸权重上限
        )
    
    def _evaluate(self, x, out, *args, **kwargs):
        # 计算收益(最大化,取负号转为最小化)
        ret = -calculate_expected_return(x)
        # 计算风险(最小化)
        risk = calculate_max_drawdown(x)
        # 计算成本(最小化)
        cost = calculate_transaction_cost(x)
        
        out["F"] = [ret, risk, cost]

# 运行优化
algorithm = NSGA2(pop_size=100)
res = minimize(
    OptionPortfolioProblem(),
    algorithm,
    ('n_gen', 200),
    seed=42,
    verbose=False
)

# 获取帕累托前沿
pareto_front = res.F
pareto_solutions = res.X

嗯,这里要注意:calculate_expected_returncalculate_max_drawdowncalculate_transaction_cost这三个函数,你得根据你的实际数据来写。我一般用历史回测数据加上蒙特卡洛模拟来估算。

如何从帕累托前沿中选一个解?

跑完NSGA-II,你会得到一堆帕累托最优解。但问题来了——你到底选哪个?

我曾经犯过一个错误:直接选了收益最高的那个解。结果呢?回撤大得离谱,实盘一个月就扛不住了。后来我学乖了,用TOPSIS方法来做决策。

TOPSIS方法的核心思路:

先找出“理想解”(所有目标都最优)和“负理想解”(所有目标都最差),然后计算每个帕累托解到理想解和负理想解的距离。选那个离理想解最近、离负理想解最远的解。

代码实现也不复杂:

def topsis_selection(pareto_front, weights):
    # pareto_front: (n_solutions, n_objectives)
    # weights: 每个目标的权重,比如 [0.4, 0.3, 0.3]
    
    # 归一化
    norm = np.sqrt((pareto_front ** 2).sum(axis=0))
    normalized = pareto_front / norm
    
    # 加权
    weighted = normalized * weights
    
    # 理想解和负理想解
    ideal = weighted.min(axis=0)  # 因为三个目标都是最小化
    neg_ideal = weighted.max(axis=0)
    
    # 计算距离
    d_plus = np.sqrt(((weighted - ideal) ** 2).sum(axis=1))
    d_minus = np.sqrt(((weighted - neg_ideal) ** 2).sum(axis=1))
    
    # 计算相对接近度
    closeness = d_minus / (d_plus + d_minus)
    
    # 选最接近理想解的那个
    best_idx = np.argmax(closeness)
    return best_idx

我个人习惯把收益的权重设得高一些,比如0.5,风险和成本各0.25。但具体怎么设,得看你的风险偏好和资金规模。

可视化帕累托前沿

光有数字不够,得画出来看看。我一般用三维散点图,把三个目标分别放在三个轴上。下面是我用matplotlib画的:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

# pareto_front 的列顺序:收益(负)、风险、成本
ax.scatter(
    -pareto_front[:, 0],  # 转回正收益
    pareto_front[:, 1],   # 风险
    pareto_front[:, 2],   # 成本
    c='blue', alpha=0.6, s=50
)

ax.set_xlabel('收益')
ax.set_ylabel('风险')
ax.set_zlabel('成本')
ax.set_title('帕累托前沿:收益-风险-成本')

# 标记选中的最优解
best_idx = topsis_selection(pareto_front, [0.5, 0.25, 0.25])
ax.scatter(
    -pareto_front[best_idx, 0],
    pareto_front[best_idx, 1],
    pareto_front[best_idx, 2],
    c='red', s=100, marker='*', label='选中解'
)
ax.legend()
plt.show()

画出来之后,你会发现帕累托前沿通常是一个曲面。曲面上的点,就是你可以选的策略组合。曲面内部的点,都是被支配的——说白了就是“有更好的选择,为啥要选它?”

避坑指南:

我曾经遇到过一个问题:帕累托前沿上的解虽然互不支配,但有些解在实盘中表现很差。为什么?因为我的风险指标只用了最大回撤,没考虑尾部风险。后来我加上了CVaR(条件风险价值),前沿的形状就变了,选出来的解也更稳健。

所以,目标函数的选择直接决定了帕累托前沿的质量。别偷懒,多试几个风险指标。

帕累托前沿的SVG结构图

下面我用一张SVG图,把整个多目标优化的流程串起来:

多目标优化流程:帕累托前沿 定义三个目标 收益、风险、成本 NSGA-II优化 种群大小100,迭代200代 帕累托前沿 非支配解集合 TOPSIS决策 选最接近理想解的点 帕累托前沿示意图(二维投影) 风险 收益 帕累托前沿 被支配解

这张图左边是二维投影(收益 vs 风险),红色曲线就是帕累托前沿。灰色点是那些被支配的解——说白了就是“既没别人赚得多,风险还比别人大”,这种策略直接扔掉就行。

实战中的注意事项

最后,分享几个我在实战中踩过的坑:

  • 目标函数要可微吗? 不一定。NSGA-II是遗传算法,不需要梯度信息。所以你可以用任何复杂的指标,比如最大回撤、夏普比率、甚至自定义的惩罚函数。
  • 种群大小怎么设? 我一般设100-200。太小了容易收敛到局部前沿,太大了计算太慢。迭代次数200-500代基本够用。
  • 权重怎么设? 如果你对某个目标特别在意,比如机构资金更看重风险控制,那把风险的权重设高一些。但别设成0,否则那个目标就完全被忽略了。
  • 别忘了约束条件。 比如单个头寸的权重不能超过20%,或者总杠杆不能超过3倍。这些约束要在问题定义里加进去,否则优化出来的解可能没法实盘。

一个小技巧:

如果你发现帕累托前沿上的解都集中在某个区域,说明你的目标函数或者约束条件可能有问题。试着调整一下目标函数的尺度,或者增加一些随机性(比如在遗传算法里加大变异率)。

好了,关于帕累托前沿在收益-风险-成本中的权衡,就聊这么多。记住一句话:没有完美的策略,只有最适合你的妥协。多目标优化的意义,就是帮你找到那个“最不坏”的选择。


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