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_return、calculate_max_drawdown、calculate_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图,把整个多目标优化的流程串起来:
这张图左边是二维投影(收益 vs 风险),红色曲线就是帕累托前沿。灰色点是那些被支配的解——说白了就是“既没别人赚得多,风险还比别人大”,这种策略直接扔掉就行。
实战中的注意事项
最后,分享几个我在实战中踩过的坑:
- 目标函数要可微吗? 不一定。NSGA-II是遗传算法,不需要梯度信息。所以你可以用任何复杂的指标,比如最大回撤、夏普比率、甚至自定义的惩罚函数。
- 种群大小怎么设? 我一般设100-200。太小了容易收敛到局部前沿,太大了计算太慢。迭代次数200-500代基本够用。
- 权重怎么设? 如果你对某个目标特别在意,比如机构资金更看重风险控制,那把风险的权重设高一些。但别设成0,否则那个目标就完全被忽略了。
- 别忘了约束条件。 比如单个头寸的权重不能超过20%,或者总杠杆不能超过3倍。这些约束要在问题定义里加进去,否则优化出来的解可能没法实盘。
一个小技巧:
如果你发现帕累托前沿上的解都集中在某个区域,说明你的目标函数或者约束条件可能有问题。试着调整一下目标函数的尺度,或者增加一些随机性(比如在遗传算法里加大变异率)。
好了,关于帕累托前沿在收益-风险-成本中的权衡,就聊这么多。记住一句话:没有完美的策略,只有最适合你的妥协。多目标优化的意义,就是帮你找到那个“最不坏”的选择。