1. 项目概述为什么高频交易模型需要“懂业务”的损失函数在量化交易领域尤其是高频算法交易High-Frequency Algorithmic Trading中我们这些从业者每天都在和数据、模型博弈。一个核心的痛点在于我们花大力气训练的机器学习模型其“优秀”的标准往往与最终的投资业绩脱节。你可能会遇到这样的情况一个LSTM模型在回测中展现出惊人的预测精度MSE均方误差低得漂亮但一旦投入实盘扣除滑点和手续费后策略收益却惨不忍睹甚至为负。这背后是传统损失函数与交易业务逻辑的根本性错配。传统的MSE、MAE等损失函数其设计哲学是“预测得越准越好”。它们平等地惩罚每一个预测误差无论这个误差是发生在价格小幅盘整时还是发生在决定盈亏的关键大波动时刻。更重要的是它们完全忽略了预测的方向对于交易决策的终极价值。在交易中一个方向正确但幅度略有偏差的预测远比一个方向错误但幅度精准的预测有价值得多。此外高频交易中频繁的买卖信号会带来巨额交易成本而传统损失函数对此视而不见极易导致模型过度优化生成大量无利可图的“噪声交易”最终在实盘中因成本侵蚀而失效。广义平均绝对方向损失Generalized Mean Absolute Directional Loss, GMADL函数正是为了解决这一根本矛盾而生。它不是另一个追求“预测精度”的学术玩具而是一个从交易员视角出发为“投资组合净值增长”这一终极目标服务的专业工具。GMADL的核心思想是将损失函数与策略的实际盈亏直接挂钩并引入参数机制让开发者能够主动控制模型对交易频率和波动幅度的敏感度。简单说它让模型训练从“做题家”模式转向了“实战派”模式。接下来我将深入拆解GMADL的设计原理、实现细节并分享如何将其集成到你的高频交易模型 pipeline 中避开那些我踩过的坑。2. GMADL 核心设计思路与原理拆解2.1 从MADL到GMADL解决不可微与灵活性瓶颈GMADL并非凭空诞生它是对其前身——平均绝对方向损失Mean Absolute Directional Loss, MADL函数的一次关键性升级。理解MADL有助于我们把握GMADL的进化逻辑。MADL函数的公式简洁而直接MADL (1/N) * Σ [ (-1) * sign(R_i * R_hat_i) * abs(R_i) ]其中R_i是实际收益率R_hat_i是预测收益率sign是符号函数abs是绝对值函数N是样本数。这个公式的巧妙之处在于它完全摒弃了对预测值R_hat_i具体数值的依赖只关心预测方向与实际方向是否一致sign(R_i * R_hat_i)。当预测方向正确时乘积为正符号函数返回1乘以-1后变为-1再乘以实际收益的绝对值最终对总损失贡献一个负值即“奖励”。反之预测错误则贡献一个正值惩罚。其损失值直接就是策略在对应样本上的单次盈亏未考虑成本。最小化MADL就是在最大化策略的累计收益。然而MADL存在两个工程上的致命缺陷不可微性sign和abs函数在零点处不可微。这在基于梯度下降的深度学习模型如LSTM、Transformer训练中是灾难性的。梯度无法平滑传播导致优化过程不稳定、难以收敛严重限制了其在复杂模型中的应用。缺乏灵活性MADL对所有正确预测的样本无论其实际波动幅度大小都给予“-|R_i|”的固定比例奖励。它无法区分一个抓住1%波动的正确预测和一个抓住10%波动的正确预测在训练中两者的“功劳”是一样的。同时它也无法针对高频交易中至关重要的“交易成本”问题进行针对性优化。GMADL的改进正是瞄准了这两点。2.2 GMADL公式解析可微性与参数化控制GMADL的公式如下GMADL (1/N) * Σ [ (-1) * ( 1 / (1 exp(-a * R_i * R_hat_i)) - 0.5 ) * abs(R_i)^b ]这个公式看起来复杂但我们可以将其分解为三个核心组件来理解方向判断与平滑处理组件[ 1 / (1 exp(-a * R_i * R_hat_i)) - 0.5 ]作用替代MADL中的sign函数用于判断预测方向是否正确。原理这里使用了Sigmoid函数σ(x) 1/(1exp(-x))的变体。当R_i * R_hat_i为正方向正确且很大时exp(-a*大正数)接近0Sigmoid值接近1减去0.5后得到约0.5。反之方向错误且误差很大时Sigmoid值接近0减去0.5后得到约-0.5。整个项的取值范围在(-0.5, 0.5)。关键参数aa控制Sigmoid函数在零点附近的斜率即方向判断的“软硬”程度。a值很大如1000Sigmoid函数在零点附近变得非常陡峭近似于一个“软”的符号函数行为非常接近MADL但对正确/错误的判定非常坚决。a值较小如1Sigmoid函数变化平缓。即使预测方向正确但乘积值不大其奖励也不会立刻达到最大反之方向错误但乘积值不大惩罚也较轻。这为模型提供了更细腻的梯度信息特别适用于波动性大、噪声多的市场环境能防止模型对噪声信号反应过度。收益幅度加权组件abs(R_i)^b作用对实际发生的收益率幅度进行加权。原理不再像MADL那样直接使用|R_i|而是对其取b次幂。关键参数bb控制模型对大波动的关注程度。b 1退化为MADL线性加权。b 1如2或5对大幅波动的样本给予超线性的权重。这意味着模型抓住一次大涨或大跌所获得的奖励或避免的惩罚将远远高于抓住多次小幅波动。这直接引导模型去学习并捕捉那些对盈亏影响最大的关键行情而非在微小的价格变动上“内卷”。b 1对大幅波动的权重降低模型会更“平等”地看待不同幅度的波动。整合与符号翻转(-1) * [方向组件] * [幅度组件]将方向组件与幅度组件相乘再乘以-1。最终当方向正确时方向组件约为0.5乘以负号后贡献负损失奖励且奖励幅度由|R_i|^b放大。方向错误时则贡献正损失惩罚。实操心得a和b这两个参数是你手中的“调音旋钮”。a调的是模型对“方向确定性”的敏感度在高噪声的Tick级数据上适当调低a能起到平滑噪声、防止过拟合的作用。b调的是模型的“风险偏好”如果你想设计一个抓大趋势、低频交易的策略就把b调高如果你的策略是做市或高频套利依赖大量微小盈利那么b接近1甚至略小于1可能更合适。2.3 如何解决过拟合与交易成本问题这是GMADL最体现其业务价值的地方。抑制过拟合Overfitting传统MSE损失函数会极力让模型拟合数据中的每一个波动包括大量无意义的噪声。GMADL通过参数b提供了解决方案。当设置b 1时模型优化的重心会自然偏向于那些带来大幅收益的样本。数据中的随机噪声通常对应着小幅波动它们在损失函数中的权重被相对降低了。因此模型不会为了完美拟合这些小噪声而扭曲其主要的学习方向从而提升了泛化能力。这相当于在损失函数层面内置了一个“去噪”和“聚焦主要矛盾”的机制。隐含的交易成本控制高频交易中交易成本手续费、滑点是利润的终极杀手。GMADL通过其独特的奖励机制间接但有效地控制了交易频率。减少无效信号如果模型产生大量方向正确但幅度极小的预测信号例如预测明天涨0.01%在b 1的设置下这些信号带来的奖励微乎其微。而一旦这些微小预测出现方向错误带来的惩罚却是实实在在的因为|R_i|虽小但方向错误贡献正损失。这种不对称性会引导模型倾向于只在它有较高把握能捕捉到一定幅度行情时才发出信号从而自动过滤掉大量不足以覆盖交易成本的“鸡肋”交易机会。与策略逻辑协同你可以将交易成本作为一个阈值融入信号生成逻辑。例如只有当模型预测的收益幅度或GMADL计算出的预期奖励超过预设的成本阈值时才执行交易。GMADL的训练目标与这一业务逻辑是自洽的它鼓励模型产生“高置信度、高预期收益”的信号。3. GMADL 的代码实现与集成实战理论再美终须落地。下面我将以PyTorch框架为例展示GMADL的实现并详细说明如何将其集成到一个典型的高频交易LSTM模型训练流程中。3.1 GMADL损失函数的PyTorch实现import torch import torch.nn as nn class GMADLLoss(nn.Module): 广义平均绝对方向损失 (GMADL) 函数。 适用于回归任务其中模型预测收益率真实值为实际收益率。 def __init__(self, a1000.0, b2.0, reductionmean): 参数: a (float): 控制方向判断Sigmoid函数斜率的参数。值越大在0点附近越接近阶跃。 b (float): 控制实际收益幅度权重的参数。b1时加大对大幅波动的关注。 reduction (str): 可选 mean (默认) 或 sum指定损失聚合方式。 super(GMADLLoss, self).__init__() self.a a self.b b self.reduction reduction def forward(self, predictions: torch.Tensor, targets: torch.Tensor) - torch.Tensor: 前向传播计算损失。 参数: predictions (Tensor): 模型预测的收益率形状为 (batch_size, ...) targets (Tensor): 真实的收益率形状需与predictions相同 返回: Tensor: 计算得到的GMADL损失值。 # 确保输入为浮点型便于计算 preds predictions.float() targs targets.float() # 计算预测值与真实值的乘积 R_i * R_hat_i product targs * preds # 核心计算方向判断组件 (可微的符号函数替代) # 1 / (1 exp(-a * product)) - 0.5 directional_component torch.sigmoid(self.a * product) - 0.5 # 计算实际收益的绝对值并施加b次幂权重 # 添加一个极小值epsilon防止梯度在|R|0处出现问题 epsilon 1e-8 magnitude_weight torch.pow(torch.abs(targs) epsilon, self.b) # 计算每个样本的损失: -1 * directional_component * magnitude_weight per_sample_loss -1.0 * directional_component * magnitude_weight # 根据reduction参数聚合损失 if self.reduction mean: loss torch.mean(per_sample_loss) elif self.reduction sum: loss torch.sum(per_sample_loss) else: loss per_sample_loss # 返回每个样本的损失 return loss # 示例用法 if __name__ __main__: # 模拟数据 batch_size 4 pred_returns torch.tensor([0.02, -0.01, 0.005, -0.03]) # 模型预测的收益率 true_returns torch.tensor([0.015, -0.005, -0.008, -0.028]) # 实际发生的收益率 # 初始化损失函数尝试不同参数 loss_fn_aggressive GMADLLoss(a1000.0, b2.0) # 激进参数坚决判断方向注重大波动 loss_fn_conservative GMADLLoss(a10.0, b1.2) # 保守参数软判断略微注重大波动 loss1 loss_fn_aggressive(pred_returns, true_returns) loss2 loss_fn_conservative(pred_returns, true_returns) print(f激进参数损失: {loss1.item():.6f}) print(f保守参数损失: {loss2.item():.6f}) # 分析对于第一个样本(方向正确幅度中等)激进参数因其b2会给与更高奖励(负损失更负)。 # 对于第三个样本(方向错误)两者都会惩罚但激进参数因a很大惩罚更坚决。3.2 集成到LSTM模型训练Pipeline假设我们已有一个用于收益率预测的简单LSTM模型。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import numpy as np # 1. 定义模型 class LSTMModel(nn.Module): def __init__(self, input_dim, hidden_dim, num_layers, output_dim): super(LSTMModel, self).__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.2) self.fc nn.Linear(hidden_dim, output_dim) def forward(self, x): # x shape: (batch, seq_len, input_dim) lstm_out, _ self.lstm(x) # 取最后一个时间步的输出 last_time_step lstm_out[:, -1, :] output self.fc(last_time_step) return output.squeeze() # 输出预测的收益率 # 2. 准备模拟数据 (这里需要你替换为真实的高频数据预处理流程) def prepare_data(price_series, seq_length60): 将价格序列转化为收益率序列并制作滑动窗口样本。 returns np.diff(np.log(price_series)) # 计算对数收益率 X, y [], [] for i in range(len(returns) - seq_length): X.append(returns[i:iseq_length]) y.append(returns[iseq_length]) # 预测下一个时刻的收益率 return np.array(X), np.array(y) # 假设 price_data 是你的高频价格序列 # X_train, y_train prepare_data(train_prices) # X_val, y_val prepare_data(val_prices) # 转换为Tensor # train_dataset TensorDataset(torch.FloatTensor(X_train).unsqueeze(-1), torch.FloatTensor(y_train)) # ... 类似处理验证集 # 3. 训练循环集成GMADL def train_model_with_gmadl(model, train_loader, val_loader, epochs100, lr0.001): device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) # 使用GMADL作为损失函数替代MSE或MAE criterion GMADLLoss(a50.0, b1.5, reductionmean) # 初始参数需调优 optimizer optim.Adam(model.parameters(), lrlr) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, patience5, factor0.5) for epoch in range(epochs): model.train() train_loss 0 for batch_X, batch_y in train_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) optimizer.zero_grad() predictions model(batch_X) loss criterion(predictions, batch_y) loss.backward() # 可选梯度裁剪防止梯度爆炸在GMADL中尤其重要当a很大时 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() * batch_X.size(0) avg_train_loss train_loss / len(train_loader.dataset) # 验证阶段 model.eval() val_loss 0 with torch.no_grad(): for batch_X, batch_y in val_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) predictions model(batch_X) loss criterion(predictions, batch_y) val_loss loss.item() * batch_X.size(0) avg_val_loss val_loss / len(val_loader.dataset) scheduler.step(avg_val_loss) if (epoch1) % 10 0: print(fEpoch [{epoch1}/{epochs}], Train Loss: {avg_train_loss:.6f}, Val Loss: {avg_val_loss:.6f}) print(训练完成。) return model # 4. 后续基于验证集GMADL损失进行超参数调优 (a, b, 模型超参) # 可以使用Optuna, Ray Tune等框架以夏普比率或卡尔玛比率作为最终优化目标。注意事项数据标准化输入模型的收益率数据建议进行标准化如Z-Score但GMADL计算损失时使用的是原始收益率或经过反标准化后的收益率因为abs(R_i)^b依赖于实际的经济意义尺度。参数初始化a和b的初始值需要根据数据特性设定。对于波动较大的加密货币a可以小一些如10-50对于波动平缓的股指a可以大一些如100-1000。b通常从1.5开始尝试。梯度监控在训练初期使用torch.autograd.grad或TensorBoard监控GMADL损失的梯度确保其稳定没有出现NaN或爆炸。a值过大可能导致Sigmoid函数区域梯度饱和。4. 参数调优与策略回测验证框架使用GMADL不是一劳永逸的a和b的选择至关重要且需要与你的具体策略逻辑如持仓周期、资产类别、成本结构相匹配。下面提供一个系统化的调优与验证流程。4.1 参数网格搜索与交叉验证我们不直接以GMADL的损失值作为最终评价标准而是以其训练出的模型在样本外回测的业绩指标为准。import itertools import pandas as pd from backtest import BacktestEngine # 假设你有一个回测引擎 def hyperparameter_tuning_gmadl(train_data, val_data, fixed_model_params): 对GMADL的a和b参数进行网格搜索。 a_values [1, 10, 50, 200, 1000] b_values [0.8, 1.0, 1.5, 2.0, 3.0] best_sharpe -float(inf) best_params {} for a, b in itertools.product(a_values, b_values): print(f\n正在测试 GMADL(a{a}, b{b})...) # 1. 用当前(a,b)训练模型 model LSTMModel(**fixed_model_params) criterion GMADLLoss(aa, bb) # ... (训练代码同上文在train_data上训练在val_data上早停) # 2. 在验证集上生成预测 val_predictions generate_predictions(model, val_data) # 3. 将预测转化为交易信号例如预测收益率阈值时做多负阈值时做空否则空仓 # 这个阈值可以基于估计的交易成本来设定 transaction_cost 0.001 # 假设单边千分之一成本 signal_threshold transaction_cost * 1.5 # 信号阈值设为成本的1.5倍 trading_signals np.where(val_predictions signal_threshold, 1, np.where(val_predictions -signal_threshold, -1, 0)) # 4. 执行回测需接入你的回测系统 # 假设backtest_engine接受信号序列和价格序列返回业绩数据 portfolio_stats backtest_engine.run( pricesval_prices, signalstrading_signals, costtransaction_cost ) # 5. 以夏普比率或卡尔玛比率作为评价标准 current_sharpe portfolio_stats[sharpe_ratio] if current_sharpe best_sharpe: best_sharpe current_sharpe best_params {a: a, b: b} print(f 发现更优参数夏普: {current_sharpe:.3f}) print(f\n最优参数组合: {best_params}, 对应夏普比率: {best_sharpe:.3f}) return best_params, best_sharpe4.2 与传统损失函数的对比回测分析为了直观展示GMADL的价值必须进行对比实验。设计一个标准的回测框架数据划分将历史数据按时间顺序分为训练集70%、验证集15%、测试集15%。测试集必须完全不在训练和参数调优过程中使用用于最终业绩评估。模型基准基准模型1 (MSE)使用MSE损失函数训练的相同LSTM/Transformer模型。基准模型2 (MAE)使用MAE损失函数训练的模型。实验模型 (GMADL)使用GMADL损失函数经过验证集调优的a, b训练的模型。统一策略逻辑所有模型使用完全相同的信号生成规则例如预测值C做多-C做空。这个阈值C可以根据训练集信号的分布来确定如取预测值的某个分位数并在整个回测中固定。业绩指标在测试集上计算并对比以下指标总收益率 / 年化收益率夏普比率 (Sharpe Ratio)衡量风险调整后收益。最大回撤 (Max Drawdown)衡量策略风险。胜率 (Win Rate)盈利交易比例。盈亏比 (Profit Factor)总盈利/总亏损。交易次数 (Number of Trades)GMADL策略的交易次数应显著少于MSE/MAE策略。单笔交易平均收益总收益/交易次数。GMADL策略此项应更高。实操心得对比回测的关键在于控制变量。除了损失函数网络结构、优化器、学习率、数据预处理等所有环节必须完全一致。否则业绩差异无法归因于损失函数。我通常会为每个损失函数跑多个随机种子取平均业绩以消除随机初始化带来的偶然性。5. 常见问题、避坑指南与进阶思考在实际应用GMADL的过程中我遇到了不少挑战也总结出一些关键经验。5.1 常见问题与解决方案速查表问题现象可能原因排查与解决方案训练损失震荡剧烈不收敛1. 参数a设置过大如10000导致Sigmoid梯度在大部分区域接近0梯度消失。2. 学习率过高。3. 数据中存在极端异常值。1. 降低a值尝试从10到500的范围观察梯度直方图。2. 使用更小的学习率如1e-4并加入学习率调度。3. 对收益率数据进行Winsorizing缩尾处理剔除极端值。模型变得非常“懒惰”几乎不发出交易信号1. 参数b设置过大如5模型过于聚焦极少数大涨大跌样本忽略了多数普通机会。2. 信号阈值C设置过高。1. 逐步调低b值如从5到2再到1.5观察训练集上信号频率的变化。2. 基于训练集预测值的分布如20%和80%分位数动态设定阈值C而非固定值。样本外回测业绩远差于样本内1. 过拟合。可能a值太小b值太大导致模型对训练集噪声过度敏感。2. 市场状态发生结构性变化。1. 增加正则化Dropout, L2权重衰减使用更简单的模型。2. 采用Walk-Forward Optimization (WFO)方法将整个回测期划分为多个滚动窗口在每个窗口内用历史数据训练、调参并在紧随其后的“样本外”区间测试。这能更好地模拟实盘中模型不断更新的过程。GMADL损失值为正但策略实际是盈利的这是正常现象GMADL的定义是“损失”其值为负表示奖励盈利为正表示惩罚亏损。最小化GMADL就是使其尽可能负。评估时关注其变化趋势而非绝对值。在训练监控时可以同时计算一个代理指标如“训练集模拟收益率”用当前模型参数在训练集上跑一遍简单策略。这个指标上升通常意味着GMADL在下降。梯度出现NaN1. 计算abs(R_i)^b时R_i可能为0导致0的0次幂未定义当b很小时。2. 数值不稳定。1. 在abs(R_i)后加一个极小值epsilon如1e-8即torch.pow(torch.abs(targets) epsilon, b)。2. 使用混合精度训练时确保计算在float32下进行。5.2 进阶思考将交易成本直接建模进损失函数论文中提到GMADL通过参数b间接控制交易频率这是一种巧妙的方式。但更激进的做法是将交易成本作为一个显式项加入到损失函数中。这需要你定义一个与预测信号变化频率相关的惩罚项。一个简化的思路是在GMADL的基础上增加一个正则化项Total_Loss GMADL λ * Transaction_Penalty其中Transaction_Penalty可以是你生成的交易信号序列的一阶差分绝对值之和衡量信号翻转次数λ是一个控制成本惩罚力度的超参数。这样模型在训练时就会直接“感知”到频繁交易带来的“损失”从而主动学习生成更平滑、更持久的信号。不过这需要对自动微分和自定义损失函数有更深的理解确保整个函数是可微的。5.3 关于Walk-Forward Optimization (WFO) 的强烈建议对于高频交易策略我强烈建议将GMADL与Walk-Forward Optimization结合使用。WFO能有效应对市场状态漂移防止静态参数过时。流程如下划分滚动窗口将总数据时长T划分为长度为W的训练窗和长度为O的测试窗O通常是一个月或一个季度。滚动训练与测试在第一个窗口[1, W]的数据上使用GMADL调参a, b训练模型。在紧接着的[W1, WO]区间上进行样本外测试记录业绩。将窗口向前滑动S步S通常等于O重复上述过程。业绩汇总将所有样本外测试区间的业绩拼接起来得到整个历史时期的、理论上更接近实盘的策略表现。这种方法计算量大但结果可信度远高于一次性的样本内训练/样本外测试。GMADL中的参数a和b可以在每个滚动窗口内重新优化以适应不同阶段的市场波动特性。GMADL不是一个魔法黑箱而是一个将业务逻辑深度嵌入模型优化过程的强大框架。它迫使你和你的模型从一开始就思考交易的终极目标——在控制风险和成本的前提下获取收益。从MSE到GMADL的转变是从一个“统计学家”思维到“交易员”思维的转变。这个过程需要大量的实验、细致的分析和严谨的回测但一旦跑通你所获得的将是一个与市场博弈逻辑同频的、更加健壮和实用的量化模型。