16、多目标优化:多目标优化问题定义、Pareto前沿、加权求和法、NSGA-II算法、多目标调优实践
做基差交易策略调优,最头疼的是什么?
不是参数太多,而是你发现——收益和回撤,天生就是一对冤家。
你调高杠杆,收益上去了,回撤也跟着飙升。你收紧止损,回撤控制住了,收益又缩水了。这就是典型的多目标优化问题。今天我们就来聊聊,怎么在多个互相打架的目标之间,找到那个最舒服的平衡点。
16.1 多目标优化问题定义
先给个定义。多目标优化,说白了就是同时优化多个目标函数。在基差交易里,常见的优化目标有:
- 最大化年化收益率
- 最小化最大回撤
- 最大化夏普比率
- 最小化交易成本
这些目标之间,往往是冲突的。你不可能让所有目标都达到最优。这就是多目标优化的核心难点——没有绝对的最优解,只有折中解。
数学定义:
给定决策变量 x,优化目标函数 f₁(x), f₂(x), ..., fₙ(x),在约束条件下,找到一组解,使得所有目标函数尽可能好。
我在项目中遇到过最典型的例子:一个螺纹钢跨期套利策略,参数调优时,年化收益从12%提到18%,但最大回撤从3%飙到了8%。你说哪个好?这得看你的风险承受能力。所以,多目标优化的本质,是帮你在帕累托前沿上做选择。
16.2 Pareto前沿
Pareto前沿,也叫帕累托最优边界。这个概念其实不复杂。
假设有两个解 A 和 B。如果 A 在所有目标上都优于或等于 B,并且至少有一个目标严格优于 B,那我们就说 A 支配 B。所有不被其他解支配的解,就构成了Pareto前沿。
举个例子。你调优一个豆粕基差策略,得到三组参数:
| 参数组 | 年化收益 | 最大回撤 |
|---|---|---|
| 方案A | 15% | 4% |
| 方案B | 18% | 6% |
| 方案C | 12% | 3% |
你看,A、B、C 之间互不支配。A收益比C高,但回撤也比C大。B收益最高,但回撤也最大。这三个方案都在Pareto前沿上。你选哪个?取决于你的偏好。
我的经验:在实际调优中,我一般会先跑出Pareto前沿,然后根据资金曲线和实盘感受,选一个自己最舒服的点。别迷信数学最优,交易是门艺术。
16.3 加权求和法
加权求和法是最直观的多目标处理方法。把多个目标加权成一个综合指标:
综合得分 = w₁ * 收益 - w₂ * 回撤 + w₃ * 夏普比率
权重 w₁、w₂、w₃ 由你设定。比如你特别在意回撤,就把 w₂ 设大一点。
听起来很简单对吧?但这里有个坑——目标量纲不同。收益是百分比,回撤也是百分比,但夏普比率是比值。直接加权,权重意义不明确。
我曾经踩过的坑:一开始直接用原始值加权,结果夏普比率权重设了0.3,收益权重设了0.5,但夏普比率数值小,几乎没起作用。后来我改成先归一化,再加权,效果就好多了。
归一化方法:
归一化值 = (原始值 - 最小值) / (最大值 - 最小值)
这样所有目标都在0到1之间,权重才有实际意义。
16.4 NSGA-II算法
加权求和法有个问题——你只能得到一个解。如果你想看整个Pareto前沿,就得反复调权重,效率很低。这时候,NSGA-II就派上用场了。
NSGA-II 是「非支配排序遗传算法」的第二版。它一次运行,就能找到一组分布在Pareto前沿上的解。核心思想就两步:
- 非支配排序:把种群分成不同层级,Pareto前沿上的解排第一层,被支配的解排后面。
- 拥挤度计算:在同一层级里,优先保留那些分布稀疏的解,保证解的多样性。
说白了,NSGA-II 就是在「找好解」和「找不同的好解」之间做平衡。我习惯用 Python 的 pymoo 库来实现:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.optimize import minimize
from pymoo.core.problem import ElementwiseProblem
class MyProblem(ElementwiseProblem):
def __init__(self):
super().__init__(n_var=3, n_obj=2, xl=[0,0,0], xu=[1,1,1])
def _evaluate(self, x, out, *args, **kwargs):
# 假设三个参数:开仓阈值、止损比例、仓位比例
threshold, stop_loss, position = x
# 这里调用你的回测函数
ret, drawdown = backtest(threshold, stop_loss, position)
out["F"] = [-ret, drawdown] # 收益取负,因为pymoo默认最小化
algorithm = NSGA2(pop_size=100)
res = minimize(MyProblem(), algorithm, termination=('n_gen', 50))
跑完之后,res.F 就是Pareto前沿上的所有解。你可以画个散点图,一目了然。
注意:NSGA-II 是启发式算法,每次运行结果可能略有不同。建议多跑几次,取稳定解。
16.5 多目标调优实践
理论说完了,来点实战。我拿一个真实的铁矿石基差套利策略举例。
策略参数有三个:
- 开仓阈值:基差偏离多少时开仓(0.5% ~ 2.0%)
- 止损比例:亏损多少止损(0.5% ~ 3.0%)
- 持仓周期:最长持有天数(5 ~ 20天)
优化目标两个:最大化年化收益、最小化最大回撤。
我用了NSGA-II,种群大小200,迭代100代。结果如下:
| 方案 | 开仓阈值 | 止损比例 | 持仓周期 | 年化收益 | 最大回撤 |
|---|---|---|---|---|---|
| P1 | 1.2% | 1.5% | 12天 | 16.3% | 4.1% |
| P2 | 0.8% | 2.0% | 15天 | 19.7% | 6.8% |
| P3 | 1.5% | 1.0% | 8天 | 13.2% | 2.9% |
这三个方案都在Pareto前沿上。我个人习惯选P1——收益不错,回撤可控。P2收益高但回撤大,适合激进型。P3回撤小但收益低,适合保守型。
避坑指南:我曾经直接选了P2,结果实盘遇到一次极端行情,回撤直接超了历史最大值。从那以后,我选参数都会留点安全边际,比如在Pareto前沿上选一个回撤比预期小20%的点。
最后,多目标优化不是一劳永逸的。市场在变,参数也要跟着调。我建议每季度重新跑一次NSGA-II,看看Pareto前沿有没有漂移。
嗯,多目标优化就聊到这儿。记住一句话:没有完美的策略,只有适合你的折中。
公众号:蓝海资料掘金营,微信deep3321