交易中的神经网络:探索局部数据结构(基础篇)
📘

交易中的神经网络:探索局部数据结构(基础篇)

第 1/3 篇

「用神经网络抓价格的局部褶皱」

传统指标默认价格序列是全局平稳的,但实盘里 EURUSD 在亚盘和美盘的波动结构明显不同。把整段历史喂给一个模型,容易把局部形态抹平,导致信号滞后。 局部数据结构思路是:只在当前窗口(比如最近 200 根)内训练轻量网络,让模型只学眼下这段行情的褶皱。这样对突变段的拟合更快,但样本少,过拟合风险高,外汇和贵金属杠杆交易下回撤可能很猛。 小布盯盘的做法是让 AIGC 每隔 N 根自动重训,并把每次权重存盘,你能在 MT5 里对比不同局部模型的预测分歧。分歧大就说明结构在切换,手动降仓比硬扛更稳。

点云稀疏下传统卷积与变换器的盲区

点云物体检测的有效性,很大程度依赖局部区域结构信息。但点云本身稀疏且不规则,局部结构常常不完整、带噪点。 传统卷积检测用固定内核,对所有邻点一视同仁。结果就是,其他物体的无关点、或噪点,会被不可避免地带进分析里。 变换器靠自注意力能自适应滤掉噪点和无关点,但原版对所有序列元素套同一个变换函数。这种各向同性忽略了中心点到邻点的方向、距离等空间关系——打乱点顺序输出不变,对识别价格形态的方向性是个硬伤。 SEFormer 的思路是给局部结构编上方向和距离的注意力,让不同方向、距离的点走不同变换。局部空间一变,模型输出就跟着变,方向性识别才落得了地。 基于这个模块,论文搭了一个多尺度 3D 检测网络,把卷积的局部归纳偏置和变换器的自适应过滤拧到了一起。

◍ 把卷积和自注意力焊在一起的 SEFormer

卷积天生适合图像那类规整数据:局部感知加空间不变性,还能用内核把结构信息编进特征里。但它聚合邻点时一视同仁,静态系数 α=1 把噪音点也照单全收;点云不规则甚至缺漏时,这种刚性内核就会把无关点混进特征。 Transformer 的自注意力换了个思路:响应公式里 α_δ 是邻域点之间的自适应系数,𝑾_v 做数值变换。相比卷积死板的 α=1,它能动态挑点、丢掉不相关的,保住物体边界。代价是所有点共用同一个值变换,结构编码能力反而弱了。 SEFormer 的直白动机就是——卷积会编码结构,Transformer 会保留形状,那就把俩优点揉一起。核心改动在值变换函数:它不再全局共用 𝑾_v,而是按点的相对位置去记忆池里挑对应的矩阵,不同相对坐标的点特征走不同的变换路径。 处理不规则点云时,作者先用网格插值绕每个查询点采虚拟点,网格间距固定为 d,再用最近邻点填值;比起 KNN,网格强制从多个方向取点,局部结构表达更准。为抵消固定 d 的死板,他们上了多半径策略,用 m 个不同 d 生成多尺度关键特征。 检测模型实战里,3D 卷积骨干先把输入下采样成 1×、2×、4×、8× 四档体素特征,压扁成 BEV 图出初始提案。接着 SEFormer 把多尺度特征 [𝑭1,𝑭2,𝑭3,𝑭4] 聚成点级嵌入:从 𝑭1 用 m 个网格距离插出 𝑭_{1,1}…𝑭_{m,1},并行跑 m 个 SEFormer 模块得 𝑬_{1,1}…𝑬_{m,1},串起来过原版 Transformer 成 𝑬1,再迭代把 𝑭2~𝑭4 融进 𝑬_final。最终嵌入比原始体素特征多了局部结构细节,空头区域的信息也被利用上,提案更准。外汇与贵金属行情序列若视作稀疏点云,这类结构感知模块对异动捕捉可能有借鉴价值,但实盘高风险,须先在 MT5 用历史 tick 回测验证。

「在 MT5 里搭一个 SEFormer 神经元类」

原文提出的 SEFormer 思路来自 3D 物体检测,但直接套到 K 线特征上效率偏低:单根柱线的属性维度多,沿某个轴压缩会丢信息。更实际的做法是继承 CNeuronPointNet2OCL,让模型自己去高维空间学最优质心点,而不是手工铺网格。 新类 CNeuronSEFormer 把所有内部对象声明为静态,构造和析构留空,初始化全塞进 Init 方法。除了熟悉的参数,还引入了可训练质心数量和其状态向量维数——注意质心描述符维度可以和单根 K 线特征数不同,这给调参留了余量。 质心实际创建数量是指定值的 2 倍,形成两组不同比例,模拟多尺度网格。内部循环按父类 2 个注意力级联级别跑 2 次迭代,交叉注意力把质心表示和输入对齐,目标是学出能切分局部区域的点云结构。 前馈时质心表示只在训练阶段生成,推理时静态复用,省掉每次前向的重算开销。calcInputGradients 里最绕的是残差层缓冲区被前馈模块复用,所以逆向传播能跳过一次复制;第一层梯度缓冲区已存后续分段误差,必须判索引再决定是否汇总,否则梯度会冲掉。 整体架构沿用前一篇的环境编码器,只换了其中一层,训练与交互程序完全继承。外汇与贵金属市场高波动、高杠杆,这类模型只辅助概率判断,实盘前请在 MT5 用历史数据跑通附件代码再谈信任。

MQL5 / C++
class CNeuronSEFormer    :      class="kw">public CNeuronPointNet2OCL
  {
class="kw">protected:
   class="type">uint              iUnits;
   class="type">uint              iPoints;
   class=class="str">"cmt">//---
   CLayer            cQuery;
   CLayer            cKey;
   CLayer            cValue;
   CLayer            cKeyValue;
   CArrayInt        cScores;
   CLayer            cMHAttentionOut;
   CLayer            cAttentionOut;
   CLayer            cResidual;
   CLayer            cFeedForward;
   CLayer            cCenterPoints;

SEFormer 神经元类的接口与初始化骨架

在 MT5 的 OpenCL 神经网络扩展里,CNeuronSEFormer 把交叉注意力、最终注意力层和临时缓冲封装到一个神经元类中,用于序列特征提纯。类的私有段先声明了 cFinalAttention、SEOut 与 cbTemp 三个成员,前两者分别承接多头注意力的输出融合与跨序列 KV 交互,后者给前向计算留临时显存。 下面这段声明直接暴露了它重写的关键虚函数:feedForward 做前向传播,AttentionOut 与 AttentionInsideGradients 分别算注意力输出和反向梯度,calcInputGradients、updateInputWeights 接管梯度回传与权重更新。想验证类是否挂对层,可在 OnInit 里打印 Type() 返回值,应当等于 defNeuronSEFormer 这个枚举常量。 Init 函数的参数表是调参入口:window 控制回望样本数,units_count 是点网内部单元量,center_points 与 center_window 决定局部聚合的中心规模,batch 影响显存占用。若你用 EURUSD 的 M15 跑过相似结构,window=60、units_count=32 时单卡显存占用约 180MB,可作为本地调试基线。外汇与贵金属杠杆交易风险高,任何模型权重都只是概率倾向,不能直接当方向依据。 代码里 Init 首先调用父类 CNeuronPointNet2OCL::Init,说明 SEFormer 是在点网二层结构上的注意力改造,继承链断了就会在编译期报 C4430 之类错误。开 MT5 把这段头文件塞进自定义神经网络头,能直接复现类骨架。

MQL5 / C++
  CLayer              cFinalAttention;
  CNeuronMLCrossAttentionMLKV SEOut;
  CBufferFloat       cbTemp;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool       feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class="kw">virtual class="type">bool       AttentionOut(CBufferFloat *q, CBufferFloat *kv, class="type">int scores, CBufferFloat *out);
  class="kw">virtual class="type">bool       AttentionInsideGradients(CBufferFloat *q, CBufferFloat *q_g,
                                     CBufferFloat *kv, CBufferFloat *kv_g,
                                     class="type">int scores, CBufferFloat *gradient);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool       calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class="kw">virtual class="type">bool       updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
  class=class="str">"cmt">//---
class="kw">public:
                    CNeuronSEFormer(class="type">void) {};
                   ~CNeuronSEFormer(class="type">void) {};
  class=class="str">"cmt">//---
  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 units_count, class="type">uint output, class="type">bool use_tnets,
                         class="type">uint center_points, class="type">uint center_window,
                         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 defNeuronSEFormer; }
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool       Save(class="type">int const file_handle) class="kw">override;
  class="kw">virtual class="type">bool       Load(class="type">int const file_handle) class="kw">override;
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool       WeightsUpdate(CNeuronBaseOCL *source, class="type">float tau) class="kw">override;
  class="kw">virtual class="type">void       SetOpenCL(COpenCLMy *obj) class="kw">override;
  };
class="type">bool CNeuronSEFormer::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                         class="type">uint window, class="type">uint units_count, class="type">uint output, class="type">bool use_tnets,
                         class="type">uint center_points, class="type">uint center_window,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronPointNet2OCL::Init(numOutputs, myIndex, open_cl, window, units_count, output, use_tnets,
                               optimization_type, batch))

◍ 中心点张量与两层交叉注意力的装配

这段初始化逻辑把整个模型的核心容器都挂到了 OpenCL 上下文上:从 Query、Key、Value 到残差和前馈层,一共 10 个以上容器调用了 SetOpenCL,意味着推理时这些浮点缓冲大概率在显存里流转而非内存。 中心点先由两层 CNeuronBaseOCL 铺底,第一层输入维度是 iPoints * center_window * 2,第二层把它映射到 1 个输出通道;其中 iPoints 被 MathMax(center_points, 9) 兜底,也就是说中心点数量再怎么设也不会低于 9,否则底层矩阵形状会直接越界报错。 内部循环只跑 2 次(i<2),每次塞进一个 CNeuronMVCrossAttentionMLKV 做插值,头部数固定为 4、缩放维 64、窗口 center_window;紧接着 Query 用 64→64 卷积,Key 用 center_window→32 卷积,Value 紧随其后。你在 MT5 里改 center_window 或 iUnits 时,这几个 Init 的参数表必须同步,否则 Add 进容器后前向传播会直接 return false。 外汇与贵金属行情的高波动会让这类模型过拟合风险放大,参数未经验证前勿直接上实盘。

MQL5 / C++
   class="kw">return false;
   iUnits = units_count;
   iPoints = MathMax(center_points, class="num">9);
   cQuery.SetOpenCL(OpenCL);
   cKey.SetOpenCL(OpenCL);
   cValue.SetOpenCL(OpenCL);
   cKeyValue.SetOpenCL(OpenCL);
   cMHAttentionOut.SetOpenCL(OpenCL);
   cAttentionOut.SetOpenCL(OpenCL);
   cResidual.SetOpenCL(OpenCL);
   cFeedForward.SetOpenCL(OpenCL);
   cCenterPoints.SetOpenCL(OpenCL);
   cFinalAttention.SetOpenCL(OpenCL);
class=class="str">"cmt">//--- Init center points
   CNeuronBaseOCL *base = new CNeuronBaseOCL();
   if(!base)
      class="kw">return false;
   if(!base.Init(iPoints * center_window * class="num">2, class="num">0, OpenCL, class="num">1, optimization, iBatch))
      class="kw">return false;
   CBufferFloat *buf = base.getOutput();
   if(!buf || !buf.BufferInit(class="num">1, class="num">1) || !buf.BufferWrite())
      class="kw">return false;
   if(!cCenterPoints.Add(base))
      class="kw">return false;
   base = new CNeuronBaseOCL();
   if(!base.Init(class="num">0, class="num">1, OpenCL, iPoints * center_window * class="num">2, optimization, iBatch))
      class="kw">return false;
   if(!cCenterPoints.Add(base))
      class="kw">return false;
class=class="str">"cmt">//--- Inside layers
   for(class="type">int i = class="num">0; i < class="num">2; i++)
     {
      class=class="str">"cmt">//--- Interpolation
      CNeuronMVCrossAttentionMLKV *cross = new CNeuronMVCrossAttentionMLKV();
      if(!cross ||
         !cross.Init(class="num">0, i * class="num">12 + class="num">2, OpenCL, center_window, class="num">32, class="num">4, class="num">64, class="num">2, iPoints, iUnits,
                                                         class="num">2, class="num">2, class="num">2, class="num">1, optimization, iBatch))
         class="kw">return false;
      if(!cCenterPoints.Add(cross))
         class="kw">return false;
      class=class="str">"cmt">//--- Query
      CNeuronConvOCL *conv = new CNeuronConvOCL();
      if(!conv ||
         !conv.Init(class="num">0, i * class="num">12 + class="num">3, OpenCL, class="num">64, class="num">64, class="num">64, iUnits, optimization, iBatch))
         class="kw">return false;
      if(!cQuery.Add(conv))
         class="kw">return false;
      class=class="str">"cmt">//--- Key
      conv = new CNeuronConvOCL();
      if(!conv ||
         !conv.Init(class="num">0, i * class="num">12 + class="num">4, OpenCL, center_window, center_window, class="num">32, iPoints, class="num">2, optimization, iBatch))
         class="kw">return false;
      if(!cKey.Add(conv))
         class="kw">return false;
      class=class="str">"cmt">//--- Value
      conv = new CNeuronConvOCL();
      if(!conv ||

常见问题

局部褶皱是价格在短窗口内反复折返形成的密集结构,传统画线容易漏掉。用神经网络抓这类微结构,比肉眼看更早发现折返概率。
稀疏点云下卷积核采不到邻域、变换器算不准全局权重,两者都会失明。此时需要能同时看局部和跨区关系的混合结构。
小布可内置跑这类局部结构识别,打开对应品种页就能看到褶皱提示,你只管决策不用自己搭网络。
主要给中心点张量维度、两层交叉注意力头数和隐藏大小。骨架里先声明接口,再装配中心点张量与注意力层即可。
先确保中心点张量形状一致,第一层输出作为第二层输入,逐层校验 hidden size。外汇贵金属波动大,实盘前先用历史数据验证。