交易中的神经网络:基于双注意力的趋势预测模型·综合运用
🧠

交易中的神经网络:基于双注意力的趋势预测模型·综合运用

(3/3)·从分段线性到 LSTM 双注意力,把论文模型真正跑成 MT5 可读信号的收尾实战

进阶 第 3/3 篇
很多人把论文里的双注意力模型当成黑箱直接套用,结果在实盘里连特征切分都没对齐时间框架。先把长期时态和短期空间特征分清楚,才谈得上让编码器-解码器替你挑重点。

「编码器后段层定义与训练入口」

上面这段是编码器从第 3 层到第 7 层的堆叠逻辑,每一层都用 CLayerDescription 描述后通过 encoder.Add 挂进网络。第 3、4 层是潜变量层,节点数由 LatentCount 控制,激活分别用 LReLU 和 SIGMOID;第 5 层输出维度为 BarDescr * NForecast,用 TANH 把值压到 [-1,1] 区间,适合价格归一化特征。 第 6 层是反归一化层(defNeuronRevInDenormOCL),layers 显式设为 1,不做激活;第 7 层是频域预测层(defNeuronFreDFOCL),window 等于 BarDescr,count 为 NForecast,probability 写死 0.7f,意味着该层以 70% 概率参与前向。任何一层 new 失败或 Add 返回 false 都会 delete 描述符并回 false,避免野指针。 Train 函数是训练触发点:先用 GetProbTrajectories(Buffer, 0.9) 拿到轨迹采样概率向量,Batch 固定 100,用 GetTickCount 记时。循环里 SampleTrajectory 选轨迹,再用 MathRand 平方分布挑起始偏移 st,若 st<=0 就回退 Batch 重采。外汇与贵金属行情下用这类自编码结构做预测属高概率试探,实盘前务必在 MT5 策略测试器跑通后再调 LatentCount 与 probability。

MQL5 / C++
class=class="str">"cmt">//--- layer class="num">3
  if(!(descr = new CLayerDescription()))
      class="kw">return false;
  descr.type = defNeuronBaseOCL;
  descr.count = LatentCount;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">4
  if(!(descr = new CLayerDescription()))
      class="kw">return false;
  descr.type = defNeuronBaseOCL;
  descr.count = LatentCount;
  descr.optimization = ADAM;
  descr.activation = SIGMOID;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">5
  if(!(descr = new CLayerDescription()))
      class="kw">return false;
  descr.type = defNeuronBaseOCL;
  descr.count = BarDescr * NForecast;
  descr.optimization = ADAM;
  descr.activation = TANH;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">6
  if(!(descr = new CLayerDescription()))
      class="kw">return false;
  descr.type = defNeuronRevInDenormOCL;
  descr.count = BarDescr * NForecast;
  descr.activation = None;
  descr.optimization = ADAM;
  descr.layers = class="num">1;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">7
  if(!(descr = new CLayerDescription()))
      class="kw">return false;
  descr.type = defNeuronFreDFOCL;
  descr.window = BarDescr;
  descr.count =  NForecast;
  descr.step = class="type">int(true);
  descr.probability = class="num">0.7f;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">void Train(class="type">void)
  {
class=class="str">"cmt">//---
  vector<class="type">float> probability = GetProbTrajectories(Buffer, class="num">0.9);
  vector<class="type">float> result, target, state;
  class="type">bool Stop = false;
  class="type">int Batch = class="num">100;
  class="type">uint ticks = GetTickCount();
class=class="str">"cmt">//---
  for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter += Batch)
    {
      class="type">int tr = SampleTrajectory(probability);
      class="type">int st = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast));
      if(st <= class="num">0)
        {
         iter -= Batch;
         class="kw">continue;
        }
      Encoder.Clear();

◍ 状态编码器的批量训练循环

这段循环是编码器离线训练的核心:每次从样本缓冲里取一批(Batch)状态向量喂给神经网络,用未来 NForecast 根 K 线的状态作监督目标做反向传播。循环上限由 MathMin(st+Batch, 总样本数-NForecast) 卡住,同时监听 IsStopped() 与自定义 Stop 标志,避免 MT5 终端人工停止或出错时死跑。 当某条状态向量的绝对值之和为零(MathAbs(state).Sum()==0),说明是空状态,直接把 iter 补偿后 break,跳过无效样本。这一判断能省掉约数万次无意义前向计算,具体占比取决于你的 Buffer 里 padding 比例。 每过 500 毫秒(GetTickCount 差值)才刷新一次 Comment 进度,显示 Encoder 的近期平均误差与百分比,避免每根样本都刷屏拖慢回测。训练结束无论成功与否都会 Print 最终误差并调用 ExpertRemove() 把 EA 从图表卸掉——这是离线训练脚本的典型收尾,不是实盘逻辑。 让小布替你跑这套 把 Batch 调到 256、NForecast 设为 5,在 EURUSD 的 M15 上跑一遍,观察 Comment 里 Error 是否从 0.1 量级降到 0.01 以下;外汇与贵金属杠杆品种波动剧烈,离线误差低不代表实盘预测概率高,需自行验证。

MQL5 / C++
for(class="type">int i = st; (i < MathMin(st + Batch, Buffer[tr].Total - NForecast) && !IsStopped() && !Stop); i++)
  {
   state.Assign(Buffer[tr].States[i].state);
   if(MathAbs(state).Sum() == class="num">0)
     {
      iter += i - st - Batch;
      class="kw">break;
     }
   bState.AssignArray(state);
   class=class="str">"cmt">//--- State Encoder
   if(!Encoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, false, (CBufferFloat*)NULL))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      Stop = true;
      class="kw">break;
     }
   class=class="str">"cmt">//--- Collect target data
   if(!Result.AssignArray(Buffer[tr].States[i + NForecast].state))
      class="kw">continue;
   if(!Result.Resize(BarDescr * NForecast))
      class="kw">continue;
   if(!Encoder.backProp(Result, (CBufferFloat*)NULL))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      Stop = true;
      class="kw">break;
     }
   if(GetTickCount() - ticks > class="num">500)
     {
      class="type">class="kw">double percent = class="type">class="kw">double(iter + i - st) * class="num">100.0 / (Iterations);
      class="type">class="kw">string str = StringFormat("%-14s %class="num">6.2f%% -> Error %class="num">15.8f\n", "Encoder", percent, Encoder.getRecentAverageError());
      Comment(str);
      ticks = GetTickCount();
     }
   }
  }
 Comment("");
class=class="str">"cmt">//---
 PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Encoder", Encoder.getRecentAverageError());
 ExpertRemove();
class=class="str">"cmt">//---
}

EURUSD 上跑通的两阶段验证

用 TPM 预测轨迹的思路落地后,先拿 EURUSD 2023 年 H1 的 H1 周期历史数据训环境编码器。它只吃历史价格走势、不评估参与者动作,所以能在初始集上一次训透,不用频繁更新,速度也快。 编码器训完,预测线和实际价贴得很紧,只是预测轨更平滑——这种平滑在后续训参与者时可能让策略更新更稳。接着进第二阶段:参与者政策迭代训练。因为动作会漂出原数据边界,经验回放缓冲区得定期用当前政策跑出的状态与奖励去补。 多轮参与者—评价者交替训完,训练集内政策是盈利的。换 2024 年 1 月同条件外推测试:共 26 笔交易,11 笔盈利,胜率刚过 42%;但平均和最大盈利都高于对应亏损,盈利因子 1.12,整体仍净赚。 风险点在月末前段——余额图出现大幅回撤,外汇/贵金属这类高杠杆品种上这种回撤很要命。模型能赚,但离直接上实盘还差一轮优调。

「模型跑通了但还没调顺」

把卷积抓短期依赖、PLR 拆长期趋势这两件事拼到一起,TPM 的思路在 MT5 里确实能落地。我们用 MQL5 把整套流程写进 EA,训完模型丢到训练集之外的行情里跑,账户权益在样本外数据上整体是正向的,说明泛化没完全失效。 不过别急着上实盘。余额曲线没有走出那种干净的单向爬坡,中间回撤肉眼可见,训练集里没见过的波动段它会犹豫甚至反向。外汇和贵金属这种高杠杆品种,回撤放大就是爆仓前奏,样本外盈利不等于能扛住极端价差。 当前这版模型只是证明路子的潜力,参数和结构都还粗。想复用的人直接把下面训练入口拷进策略测试器,先拿 EURUSD 的 H1 跑一轮,看看你机器上回撤比我们测的更深还是更浅。

◍ 把这条线请下神坛

整套 LSTM 优化方案落地到 MT5,真正跑起来的不是某个黑箱,而是一组可查文件的分工:Research.mq5 与 ResearchRealORL.mq5 负责样本采集,Study.mq5 与 StudyEncoder.mq5 做模型与编码器训练,Test.mq5 回测验证,Trajectory.mqh、NeuroNet.mqh、NeuroNet.cl 则提供状态结构与 OpenCL 加速类库,压缩包 MQL5.zip 约 1446.2 KB。 这些 mq5 与 mqh 文件直接拖进 MT5 的 MQL5 目录就能编译,但外汇与贵金属市场高杠杆、高波动,神经网络预测只代表概率倾向,不代表下一根 K 线必然重复历史轨迹。 真正该做的,是开 MT5 把 Test.mq5 接上自己的品种数据跑一遍,看回测曲线和样本外误差,再决定要不要把这条线请下神坛、放进实盘观察列表。

把特征工程交给小布盯盘
小布盯盘的 AIGC 已内置双范围特征切分与注意力权重可视化,打开对应品种页即可看到哪些短期行情片段被模型加权,你只管判断概率倾向。

常见问题

通常按品种波动周期与交易时段切分,可用内置指标计算斜率断点,间隔过细会放大噪声,过粗会丢趋势。
编码器注意力聚焦输入特征筛选,解码器注意力重权重分配历史状态对当前预测的贡献,两者串联才构成双注意力。
目前小布支持导入 ONNX 权重并映射至对应品种页,双特征提取管线可由 AIGC 自动编排,省去手写 EA 的重复劳动。
单变量缺乏跨范围上下文,模型难区分噪声与结构性偏移,双特征提取正是为补这个信息缺口。
贵金属受利率与避险情绪驱动,非线性更强,双注意力可能提升方向概率,但外汇贵金属均属高风险,须严控仓位。