神经网络在交易中的应用:市场异常的自适应检测(终篇)(基础篇)
📘

神经网络在交易中的应用:市场异常的自适应检测(终篇)(基础篇)

第 1/3 篇

「用神经网络盯住市场异常」

传统指标大多假设价格序列平稳,遇到跳空、流动性骤降或突发事件驱动的非典型波动,往往滞后甚至失效。把神经网络接进 MT5,核心目的不是预测点位,而是让模型自己从行情里学出“正常”长什么样,再标记偏离。 这篇文章是系列第一篇,先铺底:后面会围绕自适应异常检测展开,用 MQL5 把推理逻辑跑在实时行情上。外汇与贵金属杠杆高、滑点随机,任何异常信号都只是概率提示,不能直接当入场指令。 一个可落地的起点是,先用历史 Tick 或 M1 数据训练一个自编码器,重构误差超过阈值即判为异常。MT5 的 OnTick 里调用模型输出,比人工扫盘快几个数量级,但训练集必须覆盖至少一次真实流动性危机,否则模型会把异常当常态。

DADA 的三块拼图怎么拼

DADA 框架把异常检测拆成三块能各自调参的组件,不是端到端黑箱。自适应瓶颈模块会按实时行情动态改压缩率,和传统模型写死压缩参数不同,它尽量保住市场数据里最管用的特征,把干扰信息砍掉。 对抗解码器是一对:一个重建正常市况、摸清典型波动轮廓,另一个专门盯异常片段,两边对着练,误报比单解码器设置明显更少,模型也更扛造。 分块加随机掩码是处理时间序列的关键。数据被切成小段后,模型抓局部模式更利索;随机遮掉一部分逼模型自己补出来,潜在依赖关系就学到了,顺带防过拟合。这三样叠起来,异常识别准头上去,面对突发行情也不容易崩。 最实用的点是自适应:行情一变,传统算法往往得重训,DADA 自己改参数。高频场景里决策窗口就毫秒级,这种免重训的韧性直接决定能不能用。外汇和贵金属波动剧烈、杠杆风险高,拿这类模型做辅助筛选也得随时人工复核。 上篇我们落了个 CNeuronMultiWindowsConvOCL 多窗口卷积层,它虽不在 DADA 原版描述里,但在我们实现中顶替了自适应瓶颈的动态压缩职能。

◍ 用自编码器堆出自适应瓶颈

自适应瓶颈模块的核心,是把一组潜在空间压缩级别不同的自编码器当作并行小模型来用。它继承自混合专家(MoE)对象,复用其动态门控选前 k 个专家的机制,但内部不再塞通用专家,而是换成一串为时序异常检测定制的自编码器。 实际写代码时只重写了 Init 方法。父类 CNeuronMoE 里已有 cGates(选前 k 专家)和 cExperts(专家指针数组),我们跳过直接父类的初始化以免误建专家池,转而手动初始化继承组件,并按 bottlenecks 数组大小决定专家总数。 Init 里先调基础全连接层初始化,再初始化门控模块,随后用循环累加所有自编码器潜在状态的总大小。关键一步是首层卷积:卷积核数量被设为所有自编码器潜在维度之和,每个核独立把相同输入压成单值,相当于一堆微型模型并行压缩。后续多窗口卷积按各自窗口重构到目标大小,再接转置层与按自编码器数量分权重的卷积层,恢复数据布局即完成。 这套结构让每个自编码器专注子任务、动态入选,对复杂时间序列的噪声抑制和异常模式检出倾向更稳。外汇与贵金属行情高波动、高风险,这类模型只作概率性辅助,实盘前务必在 MT5 用历史数据回测验证。

MQL5 / C++
class CNeuronAdaBN   :  class="kw">public CNeuronMoE
  {
class="kw">public:
                    CNeuronAdaBN(class="type">void) {};
                   ~CNeuronAdaBN(class="type">void) {};
  class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                         class="type">uint window, class="type">uint window_out, class="type">uint units_count,
                         class="type">uint &bottlenecks[], class="type">uint top_k, class="type">uint variables,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">int       Type(class="type">void) class="kw">override  const   { class="kw">return defNeuronAdaBN; }
  };
class CNeuronMoE  :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
  CNeuronTopKGates     cGates;
  CLayer               cExperts;
  class=class="str">"cmt">//---
  ..........
  ..........
  ..........
  };
class="type">bool CNeuronAdaBN::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                        class="type">uint window, class="type">uint window_out, class="type">uint units_count,
                        class="type">uint &bottlenecks[], class="type">uint top_k, class="type">uint variables,
                        ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count * variables,
optimization_type, batch))
      class="kw">return false;
  class="type">int index = class="num">0;
  if(!cGates.Init(class="num">0, index, OpenCL, window, units_count * variables, bottlenecks.Size(), top_k, optimization, iBatch))

「搭建多窗口卷积与转置重构层」

这段初始化逻辑在 MT5 的 OpenCL 神经网络类里很典型:先清空专家容器并绑定计算设备,再依次压入卷积、多窗口卷积和转置重建层。每一层都靠 index 自增来占位,任一 Init 失败就删对象并回 false,保证栈不漏内存。 瓶颈层尺寸 bn_size 由 bottlenecks 数组累加得到,循环里 bn_size += bottlenecks[i] 把各分支通道数求和,作为首层卷积的输出通道。首层 CNeuronConvOCL 用 window×window 核映射到 bn_size,激活选 SoftPlus;随后 CNeuronMultiWindowsConvOCL 用 bottlenecks 做多尺度窗口,输出宽 window_out。 转置层 CNeuronTransposeRCDOCL 第一次把 units_count*variables 拉回 bottlenecks.Size() 通道、宽 window_out,激活直接复用前面 conv 的 Activation()。第二轮卷积把 window_out 收回到 window,激活设 None,再做一次转置还原到 window 宽。 在 MT5 里跑这套,先确认 OpenCL 设备可用,否则 Init 全返 false。外汇与贵金属行情下用此类网络做特征提取属高风险实验,过拟合概率偏高,建议先用 EURUSD 的 M1 历史做离线校验。

MQL5 / C++
  class="kw">return false;
  cExperts.Clear();
  cExperts.SetOpenCL(OpenCL);
  CNeuronConvOCL *conv = NULL;
  CNeuronMultiWindowsConvOCL *mwconv = NULL;
  CNeuronTransposeRCDOCL *transp = NULL;
  class="type">uint bn_size = class="num">0;
  for(class="type">uint i = class="num">0; i < bottlenecks.Size(); i++)
      bn_size += bottlenecks[i];
  index++;
  conv = new CNeuronConvOCL();
  if(!conv ||
      !conv.Init(class="num">0, index, OpenCL, window, window, bn_size, units_count, variables, optimization, iBatch) ||
      !cExperts.Add(conv))
    {
      class="kw">delete conv;
      class="kw">return false;
    }
  conv.SetActivationFunction(SoftPlus);
  index++;
  mwconv = new CNeuronMultiWindowsConvOCL();
  if(!mwconv ||
      !mwconv.Init(class="num">0, index, OpenCL, bottlenecks, window_out, units_count, variables, optimization, iBatch) ||
      !cExperts.Add(mwconv))
    {
      class="kw">delete conv;
      class="kw">return false;
    }
  mwconv.SetActivationFunction(SoftPlus);
  transp = new CNeuronTransposeRCDOCL();
  index++;
  if(!transp ||
      !transp.Init(class="num">0, index, OpenCL, units_count * variables, bottlenecks.Size(), window_out, optimization, iBatch) ||
      !cExperts.Add(transp))
    {
      class="kw">delete transp;
      class="kw">return false;
    }
  transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
  index++;
  conv = new CNeuronConvOCL();
  if(!conv ||
      !conv.Init(class="num">0, index, OpenCL, window_out, window_out, window, units_count * variables, bottlenecks.Size(),
optimization, iBatch) ||
      !cExperts.Add(conv))
    {
      class="kw">delete conv;
      class="kw">return false;
    }
  conv.SetActivationFunction(None);
  transp = new CNeuronTransposeRCDOCL();
  index++;
  if(!transp ||
      !transp.Init(class="num">0, index, OpenCL, bottlenecks.Size(), units_count * variables, window, optimization, iBatch) ||
      !cExperts.Add(transp))
    {
      class="kw">delete transp;
      class="kw">return false;
    }
  transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
class=class="str">"cmt">//---
  class="kw">return true;
  }

三模型线性装配而非整体封装

这一版没有把整套分析框架塞进一个黑盒对象,而是复用库里已验证的组件,把先前开发的『自适应瓶颈』拼进一条可训练的线性链路。好处是模块能单独换或调,不必重写整个代码库,编码器和解码器的结构限制也消失了,做任务特化实验更自由。 实际跑的是三个并行训练模型:环境状态编码器、Actor、价格概率预测器。编码器走经典自编码器路线,从潜在空间以最小损失重建输入,顺带挖出隐藏依赖;Actor 接管原框架里的异常解码器位置,负责识别趋势、逮反转点、下单决策;第三个模型只做未来方向概率预测,给 Actor 的信号做二次过滤。 编码器输入是终端原始多模态数据,变量分布不一,所以先接全连接层再上批量归一化把量纲拉平。随后按框架做分块掩码:20% 随机掩码直接用 Dropout 层(probability=0.2),数据先转置以适配序列处理。两个并行卷积层对非重叠片段编码,自适应瓶颈里建了 15 个潜在维为 8 倍数的小自编码器,每片段挑 3 个最合适的编码,解码器用两个连续卷积层 + tanh 激活还原。 用 tanh 不是随手选的。批归一化后数据零中心、方差近 1,按三西格玛法则约 68% 值落 [-1,1],正好卡在 tanh 值域里,异常值被自然抑住。Actor 输入账户状态向量,过全连接后与编码器潜在表示拼接,经三层全连接出动作;概率模型最简,只吃压缩环境表示,三层全连接后接 SoftMax 转概率。 所有架构在 CreateDescriptions 里用三个动态缓冲区指针定义,开头校验指针为空就 new 实例,任一分配失败直接返回 false。下面这段是编码器前几层的真实描述代码,注意批归一化 batch 设了 1e4、Dropout 概率 0.2 这两个硬参数,开 MT5 把 HistoryBars*BarDescr 换成你的样本维度就能复跑。

MQL5 / C++
class="type">bool CreateDescriptions(CArrayObj *&encoder, CArrayObj *&actor, CArrayObj *&probability)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
     {
       encoder = new CArrayObj();
       if(!encoder)
         class="kw">return false;
     }
   if(!actor)
     {
       actor = new CArrayObj();
       if(!actor)
         class="kw">return false;
     }
   if(!probability)
     {
       probability = new CArrayObj();
       if(!probability)
         class="kw">return false;
     }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronDropoutOCL;
   descr.count = prev_count;
   descr.probability = class="num">0.2f;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">3

常见问题

用自编码器类模型学习正常行情分布,重构误差突增即判为异常,省去人工定阈值。
指检测、自适应、诊断三模块;先搭瓶颈层控容量,再叠多窗口卷积,最后线性装配三模型。
小布可接入你的行情流,按设定周期跑自编码器并推送异常告警,你只看结果决策。
多窗口抓不同周期局部形态,转置层还原时序,解决单一尺度漏检的问题。
线性装配可单独调参与替换,避免整体重训,回测时定位失效模块更直观。