交易中的神经网络:具有预测编码的混合交易框架(StockFormer)·进阶篇
🧠

交易中的神经网络:具有预测编码的混合交易框架(StockFormer)·进阶篇

(2/3)·多数RL交易模型漏掉资产间隐性关联,这篇看StockFormer怎么用预测编码补上

案例拆解 第 2/3 篇

把历史价量丢进强化学习就以为模型懂市场,是常见误判。噪声环境里资产间的隐性依赖往往没被编码进状态,策略容易在最需要时失准。StockFormer 的思路是先抽隐藏态再谈决策,而不是让RL直接啃原始序列。

卷积核里的 float4 打包与梯度回传

这段代码是 OpenCL 内核里对多头卷积做向量化累加的核心片段。case 2 时输入 inp 用 float4 装了相邻 2 个特征再加常量 1 和 0,权重 weight 同样取 2 个滑动权重加一个窗口高度位置权重和 0,本质是把偏置项硬编码进向量避免额外加法。 case 3 与 default 把打包长度扩到 3 或 4,default 里四个分量全部来自 matrix_i 与 matrix_w 的连续偏移,说明当卷积窗口余数不足时仍按满 4 通道塞满 float4,利用 SIMD 宽度吃满 GPU 吞吐。 每次循环 sum += IsNaNOrInf(dot(inp, weight), 0) 都做了 NaN/Inf 防护,点积结果异常直接归零,防止贵金属跳空行情下梯度爆炸把显存写崩。外层再对 sum 做一次 IsNaNOrInf 后才送进 Activation,输出写回 matrix_o 对应偏移。 CalcHiddenGradientMHConv 内核签名里 matrix_g、matrix_ig 与 outputs、step 参数齐备,意味着隐藏层梯度会沿输入梯度矩阵回传,step 控制多头间 strides,调大 step 可能让显存带宽压力陡增,建议在 MT5 策略测试器用 EURUSD 1H 先跑 5000 根 K 线观察内核耗时。

MQL5 / C++
              inp = (float4)(matrix_i[shift_var_in + shift_in + k],
                              matrix_i[shift_var_in + shift_in + k + class="num">1], class="num">1, class="num">0);
              weight = (float4)(matrix_w[shift_var_w + shift + k], matrix_w[shift_var_w + shift + k + class="num">1],
                              matrix_w[shift_var_w + shift + window_in_h], class="num">0);
              break;
            case class="num">3:
              inp = (float4)(matrix_i[shift_var_in + shift_in + k], matrix_i[shift_var_in + shift_in + k + class="num">1],
                            matrix_i[shift_var_in + shift_in + k + class="num">2], class="num">1);
              weight = (float4)(matrix_w[shift_var_w + shift + k], matrix_w[shift_var_w + shift + k + class="num">1],
                              matrix_w[shift_var_w + shift + k + class="num">2], matrix_w[shift_var_w + shift + shift_w_h]);
              break;
            class="kw">default:
              inp = (float4)(matrix_i[shift_var_in + shift_in + k], matrix_i[shift_var_in + shift_in + k + class="num">1],
                            matrix_i[shift_var_in + shift_in + k + class="num">2], matrix_i[shift_var_in + shift_in + k + class="num">3]);
              weight = (float4)(matrix_w[shift_var_w + shift + k], matrix_w[shift_var_w + shift + k + class="num">1],
                              matrix_w[shift_var_w + shift + k + class="num">2], matrix_w[shift_var_w + shift + k + class="num">3]);
              break;
            }
        sum += IsNaNOrInf(dot(inp, weight), class="num">0);
      }
      sum = IsNaNOrInf(sum, class="num">0);
      class=class="str">"cmt">//---
      matrix_o[shift_var_out + out + shift_out] = Activation(sum, activation);;
   }
}
__kernel class="type">void CalcHiddenGradientMHConv(__global class="type">float *matrix_w,
                                        __global class="type">float *matrix_g,
                                        __global class="type">float *matrix_o,
                                        __global class="type">float *matrix_ig,
                                        const class="type">int outputs,
                                        const class="type">int step,

◍ 多头卷积核的并行梯度回传写法

在 MT5 的 OpenCL 神经层里,多头卷积(MHConv)的前向与权重更新靠全局 ID 切分数据。上面这段内核先用 get_global_id(0) 取输入索引 i、get_global_size(0) 取总输入数 inputs,再用 get_global_id(1) 取样本编号 v,三者共同算出各样本在矩阵里的偏移 shift_var_in、shift_var_out 和 shift_var_w。 窗口按 heads 均分:window_in_h = (window_in + heads - 1) / heads,window_out_h 同理。这种向上取整写法保证 head 数不能整除窗口时也不丢边界。回测中 heads=4、window_in=64 时,window_in_h 实际为 16,与直观均分一致。 梯度累加循环里,k 从 start 走到 stop,start 由 (i - window_in + step) / step 与 0 取大得出,stop 受 outputs / window_out 截断。内层按 head 和 window_out_h 展开,shift_g 越界或 shift_w 超权重组上限就 break,避免越界读显存。 最后一行 matrix_ig[shift_var_in + i] = Deactivation(sum, out, activation) 把累加梯度做反激活写回输入梯度矩阵。CNeuronMHConvOCL 类仅多一个 iHeads 成员,并 override 了 feedForward 与 updateInputWeights,说明多头逻辑收敛在这两个虚函数内,改卷积结构时优先动它们。外汇与贵金属行情的高波动可能让这类网络权重剧烈漂移,实盘前务必在 MT5 策略测试器跑多样本验证。

MQL5 / C++
const class="type">int window_in,
const class="type">int window_out,
const class="type">int activation,
const class="type">int shift_out,
const class="type">int heads
)
{
 const class="type">size_t i = get_global_id(class="num">0);
 const class="type">size_t inputs = get_global_size(class="num">0);
 const class="type">size_t v = get_global_id(class="num">1);
 const class="type">int shift_var_in = v * inputs;
 const class="type">int shift_var_out = v * outputs;
 const class="type">int shift_var_w = v * window_out * (window_in + class="num">1);
 const class="type">int window_in_h = (window_in + heads - class="num">1) / heads;
 const class="type">int window_out_h = (window_out + heads - class="num">1) / heads;
 class="type">float sum = class="num">0;
 class="type">float out = matrix_o[shift_var_in + i];
 const class="type">int w_start = i % step;
 const class="type">int start = max((class="type">int)((i - window_in + step) / step), class="num">0);
 class="type">int stop = (w_start + step - class="num">1) / step;
 stop = min((class="type">int)((i + step - class="num">1) / step + class="num">1), stop) + start;
 if(stop > (outputs / window_out))
    stop = outputs / window_out;
 for(class="type">int k = start; k < stop; k++)
    {
     class="type">int head = (k % window_out) / window_out_h;
     for(class="type">int h = class="num">0; h < window_out_h; h ++)
       {
        class="type">int shift_g = k * window_out + head * window_out_h + h;
        class="type">int shift_w = (stop - k - class="num">1) * step + (i % step) / window_in_h + head * (window_in_h + class="num">1) + h * (window_in_h + class="num">1);
        if(shift_g >= outputs || shift_w >= (window_in_h + class="num">1) * window_out)
           break;
        class="type">float grad = matrix_g[shift_out + shift_g + shift_var_out];
        sum += grad * matrix_w[shift_w + shift_var_w];
       }
    }
 matrix_ig[shift_var_in + i] = Deactivation(sum, out, activation);
}
class CNeuronMHConvOCL  :  class="kw">public CNeuronConvOCL
{
class="kw">protected:
   class="type">uint         iHeads;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;

「多头卷积层的初始化与权重分配」

在 MT5 的 OpenCL 神经网络扩展里,CNeuronMHConvOCL 用多头卷积把长窗口拆成若干子段并行处理。构造函数默认 iHeads(1),也就是退化为单头;想做多时间尺度特征提取,得在 Init 里显式传 heads 参数。 Init 方法先调基类 CNeuronProofOCL::Init,把输出维度锁成 units_count * window_out * variables,优化器硬编码 ADAM。随后 iHeads 被夹在 [1, window] 区间:MathMax(MathMin(heads, window), 1) 意味着你传超过窗口长度的头数毫无意义,传 0 也会被拉回 1。 权重缓冲区按 window_h = (iWindow + heads - 1) / heads 算每个头的子窗口,总参数量 count = (window_h + 1) * iWindowOut * iVariables。初始化缩放因子 k = 1 / sqrt(window_h + 1),循环里用 (GenerateWeight() * 2 * k - k) * WeightsMultiplier 填权重——这是带限幅的 Xavier 式初始化,head 越多子窗口越小,k 越大,单层方差倾向更稳。 动量缓冲区 FirstMomentumConv 按 count 清零并推到 GPU。若 Reserve 或 BufferCreate 任一失败,Init 直接返 false,层构建中断。开 MT5 把 heads 从 1 调到 8 对比 count 与显存占用,能直观验证子窗口切分逻辑。

MQL5 / C++
class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                    CNeuronMHConvOCL(class="type">void)  :  iHeads(class="num">1)  {};
                   ~CNeuronMHConvOCL(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 step, class="type">uint window_out,
                          class="type">uint units_count, class="type">uint variables, class="type">uint heads,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">int       Type(class="type">void)  const class="kw">override  { class="kw">return defNeuronMHConvOCL; }
  class=class="str">"cmt">//--- methods for working with files
  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="type">bool CNeuronMHConvOCL::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                            class="type">uint window, class="type">uint step, class="type">uint window_out,
                            class="type">uint units_count, class="type">uint variables, class="type">uint heads,
ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(!CNeuronProofOCL::Init(numOutputs, myIndex, open_cl, window, step,
units_count * window_out * variables, ADAM, batch))
      class="kw">return false;
   iWindowOut = window_out;
   iVariables = variables;
   iHeads = MathMax(MathMin(heads, window), class="num">1);
   const class="type">int window_h = class="type">int((iWindow + heads - class="num">1) / heads);
   const class="type">int count = class="type">int((window_h + class="num">1) * iWindowOut * iVariables);
   if(!WeightsConv)
     {
      WeightsConv = new CBufferFloat();
      if(!WeightsConv)
        class="kw">return false;
     }
   if(!WeightsConv.Reserve(count))
     class="kw">return false;
   class="type">float k = (class="type">float)(class="num">1 / sqrt(window_h + class="num">1));
   for(class="type">int i = class="num">0; i < count; i++)
     {
      if(!WeightsConv.Add((GenerateWeight() * class="num">2 * k - k) * WeightsMultiplier))
        class="kw">return false;
     }
   if(!WeightsConv.BufferCreate(OpenCL))
     class="kw">return false;
   if(!FirstMomentumConv)
     {
      FirstMomentumConv = new CBufferFloat();
      if(!FirstMomentumConv)
        class="kw">return false;
     }
   if(!FirstMomentumConv.BufferInit(count, class="num">0.0))
     class="kw">return false;
   if(!FirstMomentumConv.BufferCreate(OpenCL))

多头卷积与前向网络的初始化衔接

这段代码片段展示了一个多头卷积层容器 CNeuronMHConvOCL 在初始化时的内存与 OpenCL 缓冲申请逻辑,以及继承自 CNeuronBaseOCL 的前向网络类 CNeuronMHFeedForward 的骨架。 先看卷积缓冲的失败兜底:若 SecondMomentumConv 指针为空,就 new 一个 CBufferFloat 实例,再次判空失败直接 return false;随后调用 BufferInit(count, 0.0) 把长度 count 的浮点缓冲预填 0.0,再 BufferCreate(OpenCL) 把缓冲推到 OpenCL 设备。任一环节返回 false 都会中断 Init,避免悬空 GPU 资源。 注意 if(!!DeltaWeightsConv) delete DeltaWeightsConv; 这一行——双叹号把指针强转布尔,若旧权重缓冲存在就先释放,防止重复 Init 时内存泄漏。 CNeuronMHFeedForward 内部用 acConvolutions[2] 固定挂了两个卷积子层,说明该网络结构预设双头并行特征提取。其 Init 参数暴露了关键维度:window 与 window_out 控制时间窗映射,units_count、variables、heads 决定多头拆分粒度,batch 影响 OpenCL 核的并行粒度。开 MT5 把 heads 从 2 调到 4,可能改变显存占用与收敛倾向,外汇与贵金属品种上需警惕过拟合带来的回测失真高风险。

MQL5 / C++
   class="kw">return false;
class=class="str">"cmt">//---
   if(!SecondMomentumConv)
     {
      SecondMomentumConv = new CBufferFloat();
      if(!SecondMomentumConv)
         class="kw">return false;
     }
   if(!SecondMomentumConv.BufferInit(count, class="num">0.0))
      class="kw">return false;
   if(!SecondMomentumConv.BufferCreate(OpenCL))
      class="kw">return false;
   if(!!DeltaWeightsConv)
      class="kw">delete DeltaWeightsConv;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronMHFeedForward   :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   CNeuronMHConvOCL  acConvolutions[class="num">2];
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                     CNeuronMHFeedForward(class="type">void) {};
                    ~CNeuronMHFeedForward(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 window_out,
                           class="type">uint units_count, class="type">uint variables, class="type">uint heads,
                           ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void) const class="kw">override  {  class="kw">return defNeuronMHFeedForward;  }
   class=class="str">"cmt">//--- methods for working with files
   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">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
   };
class="type">bool CNeuronMHFeedForward::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 variables, class="type">uint heads,
                                ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {

◍ 多头前馈与交叉注意力的初始化链路

CNeuronMHFeedForward 的初始化里,第一层卷积 acConvolutions[0] 用 GELU 激活,第二层 acConvolutions[1] 故意设成 None 并接管梯度回传,这种「前层非线性 + 后层线性」的搭配在 MT5 的 OpenCL 神经网里常用于抑制过拟合。 feedForward 方法用 for 循环把 acConvolutions 串成链:prev 指针从外部 NeuronOCL 起,每过一层就换成当前卷积层的指针,最后调用 SumAndNormilize 做窗口长度为 acConvolutions[0].GetWindow() 的求和归一。注意归一时倒数第二个参数填 1,代表在特征维度上做缩放。 反向的 calcInputGradients 从倒数第二层往前循环到 0,先算隐藏梯度再回灌给 NeuronOCL;若 NeuronOCL 自身激活为 None 就直接 SumAndNormilize 梯度,否则先走 DeActivation 再归一。外汇与贵金属行情噪声大,这类结构在 EURUSD 15M 上回测可能降低伪信号率,但实盘仍属高风险,参数窗口不对容易欠拟合。 CNeuronCrossDMHAttention 直接继承 CNeuronRMAT,说明交叉多头注意力准备复用相对位置矩阵的逻辑,开 MT5 把这两段挂到自定义指标里跑一遍,能直观看到 GELU 层输出的分布差异。

MQL5 / C++
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count * variables, optimization_type, batch))
    class="kw">return false;
if(!acConvolutions[class="num">0].Init(class="num">0, class="num">0, OpenCL, window, window, window_out, units_count, variables, heads,
optimization, iBatch))
    class="kw">return false;
acConvolutions[class="num">0].SetActivationFunction(GELU);
if(!acConvolutions[class="num">1].Init(class="num">0, class="num">1, OpenCL, window_out, window_out, window, units_count, variables, heads,
optimization, iBatch))
    class="kw">return false;
acConvolutions[class="num">1].SetActivationFunction(None);
if(!SetGradient(acConvolutions[class="num">1].getGradient(), true))
    class="kw">return false;
SetActivationFunction(None);
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronMHFeedForward::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
  CObject *prev = NeuronOCL;
  for(class="type">uint i = class="num">0; i < acConvolutions.Size(); i++)
    {
      if(!acConvolutions[i].FeedForward(prev))
        class="kw">return false;
      prev = GetPointer(acConvolutions[i]);
    }
  if(!SumAndNormilize(NeuronOCL.getOutput(), acConvolutions[acConvolutions.Size() - class="num">1].getOutput(),
                      Output, acConvolutions[class="num">0].GetWindow(), true, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronMHFeedForward::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
  if(!NeuronOCL)
    class="kw">return false;
  for(class="type">int i = (class="type">int)acConvolutions.Size() - class="num">2; i >= class="num">0; i--)
    {
      if(!acConvolutions[i].calcHiddenGradients(acConvolutions[i + class="num">1].AsObject()))
        class="kw">return false;
    }
  if(!NeuronOCL.calcHiddenGradients(acConvolutions[class="num">0].AsObject()))
    class="kw">return false;
  if(NeuronOCL.Activation() == None)
    {
      if(!SumAndNormilize(NeuronOCL.getGradient(), Gradient, NeuronOCL.getGradient(),
                          acConvolutions[class="num">0].GetWindow(), false, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
    }
  else
    {
      if(!DeActivation(NeuronOCL.getOutput(), NeuronOCL.getPrevOutput(), Gradient, NeuronOCL.Activation()) ||
      !SumAndNormilize(NeuronOCL.getGradient(), NeuronOCL.getPrevOutput(), NeuronOCL.getGradient(),
                       acConvolutions[class="num">0].GetWindow(), false, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class CNeuronCrossDMHAttention   :  class="kw">public CNeuronRMAT
  {
把跨资产诊断交给小布
小布盯盘已内置多资产相关性异动扫描,打开对应品种页即可看到哪些标的的联动在悄悄变形,你只需判断要不要调仓。

常见问题

长期分支偏向捕捉跨周期趋势形态,短期分支聚焦近段波动结构,二者通过DMH-Attn并行FFN在不同子空间提取时态特征,再经融合层统一。
目前小布提供的是相关性和隐态异动的可视化诊断,并不托管训练RL策略;重度建模仍需自有算力,但看盘口的重复劳动可交给小布。
评论者给出的价值误差反向传播进编码层,能让隐藏态的抽取方向贴合策略收益,而非只做无监督重构,从而避免表征与交易目标脱节。
它将单个前馈网络拆成一组并行FFN,在多头注意力后分头处理子特征,等效拓宽了特征分解通道,无需堆参也能抓更细的时态模式。