第 7 章 · 03 损失函数 本节摘要:本节讲清 Hyperopt 的「方向盘」——损失函数(loss function)。损失函数定义了「什么叫好」,Hyperopt 会朝它指向的方向搜索。freqtrade 内置十余种损失函数,它们各有侧重: 只看利润、 优化风险调整后收益、 压回撤、 多指标综合。选错损失函数,Hyperopt 可能把策略推向「高利润但巨回撤」或「稳但不赚」的极端。本节先讲损失函数在 Hyperopt 闭环里的角色,再用一张表对比所有内置损失函数的侧重和适用场景,给出选择决策树;然后挑两个代表性实现( 和 )看它们的代码逻辑;最后讲怎么用 写自定义损失函数,以及「损失函数越小越好」的方向约定。
本节摘要:本节讲清 Hyperopt 的「方向盘」——损失函数(loss function)。损失函数定义了「什么叫好」,Hyperopt 会朝它指向的方向搜索。freqtrade 内置十余种损失函数,它们各有侧重:
OnlyProfitHyperOptLoss只看利润、SharpeHyperOptLoss优化风险调整后收益、MaxDrawDownHyperOptLoss压回撤、MultiMetricHyperOptLoss多指标综合。选错损失函数,Hyperopt 可能把策略推向「高利润但巨回撤」或「稳但不赚」的极端。本节先讲损失函数在 Hyperopt 闭环里的角色,再用一张表对比所有内置损失函数的侧重和适用场景,给出选择决策树;然后挑两个代表性实现(SharpeHyperOptLoss和MultiMetricHyperOptLoss)看它们的代码逻辑;最后讲怎么用IHyperOptLoss写自定义损失函数,以及「损失函数越小越好」的方向约定。
内容来源:原项目文档
docs/hyperopt.md(Loss functions)、docs/advanced-hyperopt.md(Custom loss)、源码freqtrade/optimize/hyperopt_loss/,汉化并套用体系化模板。
阅读完本节,你应当能够:
回顾第 01 节的 Hyperopt 闭环:
损失函数是闭环里的评分器:它把一次回测的所有结果(交易列表、统计、起止余额等)压缩成一个标量 float——这个数越小,代表结果越好。Optuna 采样器根据这个分数决定下一组参数往哪探索。
💡 方向约定:所有损失函数都遵循「返回值越小越好」。所以像 Sharpe 这种「越大越好」的指标,损失函数会返回
-sharpe(取负),让它变成「越小(越负)= Sharpe 越大 = 越好」。
损失函数通过 --hyperopt-loss <类名> 指定:
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy MyStrategy -e 500
或写进配置:
{"hyperopt_loss": "SharpeHyperOptLossDaily"}
freqtrade 在 freqtrade/optimize/hyperopt_loss/ 下内置了 13 种损失函数。按「侧重维度」分类:
| 类名 | 侧重 | 适用 |
|---|---|---|
OnlyProfitHyperOptLoss |
只看总利润 | 只关心赚多少,不管波动 |
ShortTradeDurHyperOptLoss |
短持仓 + 避亏(旧默认) | 偏好快进快出的短线 |
| 类名 | 侧重 | 适用 |
|---|---|---|
SharpeHyperOptLoss |
Sharpe 比率(收益/总波动) | 想要「平滑上升」的资金曲线 |
SharpeHyperOptLossDaily |
日频 Sharpe(推荐) | 比按交易更稳定,抑制单笔异常 |
SortinoHyperOptLoss |
Sortino(只算下行波动) | 不惩罚上涨波动 |
SortinoHyperOptLossDaily |
日频 Sortino | 同上,日频更稳 |
CalmarHyperOptLoss |
Calmar(年化收益/最大回撤) | 同时关注收益与回撤 |
| 类名 | 侧重 | 适用 |
|---|---|---|
MaxDrawDownHyperOptLoss |
最小化最大绝对回撤 | 极度厌恶回撤 |
MaxDrawDownRelativeHyperOptLoss |
绝对 + 相对回撤兼顾 | 平衡型 |
MaxDrawDownPerPairHyperOptLoss |
按交易对算盈亏/回撤,取最差 | 多币策略,防某币拖后腿 |
ProfitDrawDownHyperOptLoss |
最大利润 + 最小回撤 | 平衡型,DRAWDOWN_MULT 可调 |
| 类名 | 侧重 | 适用 |
|---|---|---|
MultiMetricHyperOptLoss |
多指标综合(利润+回撤+PF+期望+胜率+交易数) | 新手默认推荐,平衡不易走极端 |
MultiMetricHyperOptLoss。它综合多维度,最不易把策略推向极端。SharpeHyperOptLossDaily(官方 quick-start 示例就用它)。MaxDrawDownPerPairHyperOptLoss,防止一两个好币掩盖其他币的亏损。OnlyProfitHyperOptLoss:它只看利润,容易优化出「高利润但巨回撤、靠一两次极端行情」的策略,实盘心理难承受。💡 可以多跑几次对比:同一策略,分别用 Sharpe、Sortino、MultiMetric 各跑一轮,看找出的参数是否一致。如果不同损失函数都收敛到相近参数,说明策略稳健;如果差异巨大,说明它在某个维度上是「勉强凑出来的」。
最经典的风险调整损失,源码极简(freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py):
class SharpeHyperOptLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function(results, min_date, max_date, starting_balance, *args, **kwargs): sharp_ratio = calculate_sharpe(results, min_date, max_date, starting_balance) return -sharp_ratio
逻辑就一句话:算 Sharpe,取负返回。Sharpe = (平均收益 - 无风险收益) / 收益标准差,衡量「每单位波动换来的收益」。-sharp_ratio 让「Sharpe 越大 → 返回值越小 → Hyperopt 觉得越好」。
SharpeHyperOptLossDaily 是它的日频版本:用每日收益而非每笔交易收益算 Sharpe,对「单笔异常巨利」不敏感,更稳健。
新手推荐的综合性损失(hyperopt_loss_multi_metric.py),它综合了 6 个维度:
# 简化版逻辑 total_profit = results["profit_abs"].sum() profit_factor = winning_profit / (abs(losing_profit) + 1e-6) _, expectancy_ratio = calculate_expectancy(results) winrate = len(winning_trades) / len(results) relative_account_drawdown = calculate_max_drawdown(...)["relative_account_drawdown"] # 交易数过少则惩罚 trade_count_penalty = 1.0 if trade_count >= 50 else max(0.1, 1 - ...) # 综合:利润 - 回撤折扣,再乘以各因子的对数 profit_draw_function = total_profit - (relative_account_drawdown * total_profit) * (1 - DRAWDOWN_MULT) return -1 * (profit_draw_function * log(profit_factor + PF_CONST) * log(min(10, expectancy_ratio) + EXPECTANCY_CONST) * log(WINRATE_CONST + winrate) * trade_count_penalty)
设计要点:
log(... + const) 避免某个因子过大主导,也让边际收益递减。💡 为什么推荐 MultiMetric:它的乘法结构天然抑制极端——只刷利润不管回撤的策略,会被回撤折扣拉低;只刷胜率但单笔小的,会被 expectancy 拉低。结果更「全面发展」。
源码逻辑(hyperopt_loss_max_drawdown_per_pair.py):
score_per_pair = [] for p in backtest_stats["results_per_pair"]: if p["key"] != "TOTAL": profit = p.get("profit_total_abs", 0) drawdown = p.get("max_drawdown_abs", 0) profit_dd = profit / drawdown if drawdown != 0 and profit != 0 else profit score = profit_dd - penalty if profit_dd < 1.0 else profit_dd score_per_pair.append(score) return -min(score_per_pair) # 取最差的那个币的分数
关键在最后一行:-min(score_per_pair)——它取所有交易对里表现最差的那个作为整体分数。这样 Hyperopt 被迫同时优化所有币,不能靠一两个好币掩盖其他币的亏损。多币策略强烈推荐。
内置的不够用?写一个。两步:
在 user_data/hyperopts/ 下新建文件(如 my_loss.py),继承 IHyperOptLoss,实现 hyperopt_loss_function:
from datetime import datetime from pandas import DataFrame from freqtrade.constants import Config from freqtrade.optimize.hyperopt import IHyperOptLoss class MyCustomLoss(IHyperOptLoss): @staticmethod def hyperopt_loss_function( *, results: DataFrame, # 交易 DataFrame trade_count: int, # = len(results) min_date: datetime, max_date: datetime, config: Config, processed: dict, # {pair: DataFrame} backtest_stats: dict, # 回测统计 starting_balance: float, **kwargs, ) -> float: # 你的打分逻辑:越小越好 total_profit = results['profit_ratio'].sum() winrate = (results['profit_ratio'] > 0).mean() # 例:利润 * 胜率,取负 return -(total_profit * winrate)
可用的参数(results DataFrame 的列):
pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, exit_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs
backtest_stats 含 results_per_pair 等结构(见 optimize_reports.py 的 generate_strategy_stats())。
freqtrade hyperopt --hyperopt-loss MyCustomLoss --strategy MyStrategy -e 500
*args, **kwargs:接口未来可能扩展,留着兼容。⚠️ 方向别写反:记住「返回值越小越好」。如果你用「越大越好」的指标(如利润、Sharpe、胜率),记得取负或用
1 - x之类变换。
--hyperopt-loss 指定。return -calculate_sharpe(...),取负让大 Sharpe = 小损失。-min(score_per_pair) 取最差币,强制全币优化,多币护身符。IHyperOptLoss,实现 hyperopt_loss_function,放 user_data/hyperopts/,保持 **kwargs,返回 float,方向别写反。下一节(第 7 章末节),我们讲高级优化——随机 vs TPE 等采样器选择、产出 JSON 怎么导入策略、以及避免过拟合的实战清单。