交易中的神经网络:配备注意力机制(MASAAT)的智代融汇·进阶篇
📘

交易中的神经网络:配备注意力机制(MASAAT)的智代融汇·进阶篇

第 2/2 篇

◍ GPU 内核里怎么挑转折点

在 MT5 的 OpenCL 内核里做批量拐点识别,先得把数据维度理清。get_global_id(1) 和 get_global_size(1) 拿到的是变量维的索引与总数,get_global_id(2) 与 get_global_size(2) 对应 agent 维,这意味着同一批计算可以并行铺在多个品种或模型上跑。 transpose 标志决定输入矩阵的读取方式:为真时按 variables 跨步取数,为假时沿单变量序列逐点取。shift_ag 把每个 agent 的缓冲区整体偏移到 a*lenth*variables,避免互相踩内存。 拐点(ttp)的判断不是简单看相邻两根。代码从当前点 i 向前后各扫一遍,只要价格波动没超过 min_step[a] 就继续追溯,记录这段「压缩区间」里的最高、最低位置。若 i 是序列首尾,直接判为拐点;否则只有当 i 自身是局部极值、且极值位置正好落回 i 时,才置 bttp=true。 实际调参时,min_step[a] 就是拐点灵敏度旋钮:设太小,EURUSD 的 M15 上可能一屏标出几十个拐点;设太大,又容易漏掉真正的价格行为转折。外汇与贵金属杠杆高,这种识别仅作结构参考,信号失效概率不低,务必先在策略测试器里用历史数据验证。

MQL5 / C++
const class="type">size_t v = get_global_id(class="num">1);
const class="type">size_t variables = get_global_size(class="num">1);
const class="type">size_t a = get_global_id(class="num">2);
const class="type">size_t agents = get_global_size(class="num">2);
class=class="str">"cmt">//--- constants
const class="type">int shift_in = ((class="type">bool)transpose ? (i * variables + v) : (v * lenth + i));
const class="type">int step_in = ((class="type">bool)transpose ? variables : class="num">1);
const class="type">int shift_ag = a * lenth * variables;
class=class="str">"cmt">//--- look for ttp
class="type">class="kw">float value = IsNaNOrInf(inputs[shift_in], class="num">0);
class="type">bool bttp = false;
if(i == class="num">0 || i == lenth - class="num">1)
    bttp = true;
else
  {
    class="type">class="kw">float prev = value;
    class="type">int prev_pos = i;
    class="type">class="kw">float max_v = value;
    class="type">class="kw">float max_pos = i;
    class="type">class="kw">float min_v = value;
    class="type">class="kw">float min_pos = i;
    while(fmax(fabs(prev - max_v), fabs(prev - min_v)) < min_step[a] && prev_pos > class="num">0)
      {
       prev_pos--;
       prev = IsNaNOrInf(inputs[shift_in - (i - prev_pos) * step_in], class="num">0);
       if(prev >= max_v && (prev - min_v) < min_step[a])
         {
          max_v = prev;
          max_pos = prev_pos;
         }
       if(prev <= min_v && (max_v - prev) < min_step[a])
         {
          min_v = prev;
          min_pos = prev_pos;
         }
      }
    class="type">class="kw">float next = value;
    class="type">int next_pos = i;
    while(fmax(fabs(next - max_v), fabs(next - min_v)) < min_step[a] && next_pos < (lenth - class="num">1))
      {
       next_pos++;
       next = IsNaNOrInf(inputs[shift_in + (next_pos - i) * step_in], class="num">0);
       if(next > max_v && (next - min_v) < min_step[a])
         {
          max_v = next;
          max_pos = next_pos;
         }
       if(next < min_v && (max_v - next) < min_step[a])
         {
          min_v = next;
          min_pos = next_pos;
         }
      }
    if(
       (value >= prev && value > next) ||
       (value > prev && value == next) ||
       (value <= prev && value < next) ||
       (value < prev && value == next)
    )
      if(max_pos == i || min_pos == i)
          bttp = true;

在 OpenCL 内核里用最小二乘估局部趋势

这段内核代码承接上一节的转折点标记,真正开始算「当前段」的斜率与截距。它只在 pos 落在 (0, lenth/3) 区间时才做拟合,意味着若已出现超过总长度三分之一的转折点,就改用整段剩余距离来算,避免短窗口把噪声当趋势。 拟合用的是标准一元线性回归:遍历 dist 根柱子,累加 x、y、xy、xx,再套 slope = (n·Σxy − Σx·Σy) / (n·Σxx − Σx²)。dist 为 1 时分母强制取 1,防止除零;所有中间值过 IsNaNOrInf 兜底,NaN 或 Inf 会被夹成 0。 算完的 three 个量写回 outputs:shift_out 放斜率,shift_out+step_in 放截距,shift_out+2·step_in 放 dist/lenth 这个「段长占比」。外汇与贵金属波动跳变多,dist/lenth 偏小往往说明转折密集、趋势倾向弱化,实盘需结合别的条件过滤。 把 transpose 置真时,索引从「变量优先」翻成「样本优先」,同样的拟合逻辑能直接喂给批量矩阵。开 MT5 把这段塞进你自己的 OpenCL 程序,改 lenth 从 64 调到 128,观察 slope 输出对毛刺的敏感度变化。

MQL5 / C++
  }
  isttp[shift_in + shift_ag] = (class="type">int)bttp;
  outputs[shift_in + shift_ag] = class="num">0;
  barrier(CLK_LOCAL_MEM_FENCE);
class=class="str">"cmt">//--- calc position
  class="type">int pos = -class="num">1;
  class="type">int prev_in = class="num">0;
  class="type">int prev_ttp = class="num">0;
  if(bttp)
    {
    pos = class="num">0;
    for(class="type">int p = class="num">0; p < i; p++)
      {
      class="type">int current_in = ((class="type">bool)transpose ? (p * variables + v) : (v * lenth + p));
      if((class="type">bool)isttp[current_in + shift_ag])
        {
        pos++;
        prev_ttp = p;
        prev_in = current_in;
        }
      }
    }
class=class="str">"cmt">//--- cacl tendency
  if(pos > class="num">0 && pos < (lenth / class="num">3))
    {
    class="type">class="kw">float sum_x = class="num">0;
    class="type">class="kw">float sum_y = class="num">0;
    class="type">class="kw">float sum_xy = class="num">0;
    class="type">class="kw">float sum_xx = class="num">0;
    class="type">int dist = i - prev_ttp;
    for(class="type">int p = class="num">0; p < dist; p++)
      {
      class="type">class="kw">float x = (class="type">class="kw">float)(p);
      class="type">class="kw">float y = IsNaNOrInf(inputs[prev_in + p * step_in], class="num">0);
      sum_x += x;
      sum_y += y;
      sum_xy += x * y;
      sum_xx += x * x;
      }
    class="type">class="kw">float slope = IsNaNOrInf((dist * sum_xy - sum_x * sum_y) / (dist > class="num">1 ? (dist * sum_xx - sum_x * sum_x) : class="num">1), class="num">0);
    class="type">class="kw">float intercept = IsNaNOrInf((sum_y - slope * sum_x) / dist, class="num">0);
    class="type">int shift_out = ((class="type">bool)transpose ? ((pos - class="num">1) * class="num">3 * variables + v) : (v * lenth + (pos - class="num">1) * class="num">3)) + shift_ag;
    outputs[shift_out] = slope;
    outputs[shift_out + step_in] = intercept;
    outputs[shift_out + class="num">2 * step_in] = ((class="type">class="kw">float)dist) / lenth;
    }
  else
    {
    if(pos == (lenth / class="num">3))
      {
      class="type">class="kw">float sum_x = class="num">0;
      class="type">class="kw">float sum_y = class="num">0;
      class="type">class="kw">float sum_xy = class="num">0;
      class="type">class="kw">float sum_xx = class="num">0;
      class="type">int dist = lenth - prev_ttp;
      for(class="type">int p = class="num">0; p < dist; p++)
        {
        class="type">class="kw">float x = (class="type">class="kw">float)(p);
        class="type">class="kw">float y = IsNaNOrInf(inputs[prev_in + p * step_in], class="num">0);
        sum_x += x;
        sum_y += y;
        sum_xy += x * y;
        sum_xx += x * x;

「分段线性回归的多智能体封装」

在分段线性回归(PLR)单层的基础上,CNeuronPLRMultiAgentsOCL 把多个代理(agent)的距离阈值并行管理。每个代理持有独立的 min_distance 值,初始化时由 iAgents = (int)min_distance.Size() 决定并行规模;若向量为空则返回 0,调用方需自行拦截。 类内部用 CBufferFloat cMinDistance 缓存各代理的最小距离参数,feedForward 与 calcInputGradients 均被声明为虚函数,便于在 OpenCL 后端做前向与反向的定制。 Init 方法的入参同时接收 window_in、units_count、transpose 以及 min_distance 引用,说明该层支持非转置与转置两种张量排布。实盘跑 MT5 时,若你传入的 min_distance 长度为 3,则这一层会并行算 3 条不同灵敏度的分段回归线,输出斜率、截距与覆盖比例三类张量。 从代码碎片看,单代理输出按 step_in 间隔写入 outputs:shift_out 存斜率,shift_out+step_in 存截距,shift_out+2*step_in 存 dist/lenth 的归一化覆盖度。这种排布直接决定了后续层读取偏移,改窗口长度前先在纸面算一遍 shift_ag 避免越界。

MQL5 / C++
      }
      class="type">class="kw">float slope = IsNaNOrInf((dist * sum_xy - sum_x * sum_y) / (dist > class="num">1 ? (dist * sum_xx - sum_x * sum_x) : class="num">1),class="num">0);
      class="type">class="kw">float intercept = IsNaNOrInf((sum_y - slope * sum_x) / dist, class="num">0);
      class="type">int shift_out = ((class="type">bool)transpose ? ((pos - class="num">1) * class="num">3 * variables + v) : (v * lenth + (pos - class="num">1) * class="num">3)) + shift_ag;
      outputs[shift_out] = slope;
      outputs[shift_out + step_in] = intercept;
      outputs[shift_out + class="num">2 * step_in] = IsNaNOrInf((class="type">class="kw">float)dist / lenth, class="num">0);
      }
   }
}
class CNeuronPLRMultiAgentsOCL  :  class="kw">public CNeuronPLROCL
{
class="kw">protected:
   class="type">int            iAgents;
   CBufferFloat   cMinDistance;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool   feedForward(CNeuronBaseOCL *NeuronOCL);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool   calcInputGradients(CNeuronBaseOCL *prevLayer);
class="kw">public:
                  CNeuronPLRMultiAgentsOCL(class="type">void)  : iAgents(class="num">1) {};
                 ~CNeuronPLRMultiAgentsOCL(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_in, class="type">uint units_count, class="type">bool transpose,
                       vector<class="type">class="kw">float> &min_distance,
                       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">return defNeuronPLRMultiAgentsOCL;  }
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool   Save(class="type">int const file_handle);
   class="kw">virtual class="type">bool   Load(class="type">int const file_handle);
   class="kw">virtual class="type">void   SetOpenCL(COpenCLMy *obj);
};
class="type">bool CNeuronPLRMultiAgentsOCL::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                                    class="type">uint window_in, class="type">uint units_count, class="type">bool transpose,
                                    vector<class="type">class="kw">float> &min_distance,
                                    ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
   {
   iAgents = (class="type">int)min_distance.Size();
   if(iAgents <= class="num">0)

◍ 横截面注意力层的初始化与继承结构

在 MT5 的 OpenCL 神经网络扩展里,CNeuronCrossSectionalAnalysis 直接继承自 CNeuronMVMHAttentionMLKV,说明它复用了多头注意力与键值压缩的前向逻辑,只是在此之上叠加了横截面特征提取。 类内保护了 cEmbeding(卷积嵌入)与 cTransposeRCD(转置算子)两个成员,feedForward、calcInputGradients、updateInputWeights 均被 override,意味着梯度回传路径完全按横截面语义重写,而不是套用父类的序列处理。 Init 函数入口先调用父类 CNeuronBaseOCL::Init,传入 window_in * units_count * iAgents 作为窗口展平长度;任意一步缓冲区创建失败(如 icIsTTP < 0 或 cMinDistance 赋值异常)就 return false,这种链式失败返回在实盘加载自定义模型时容易导致静默初始化中断,需在 EA 日志里显式捕获。 下面的代码段展示了基类初始化与子类声明的关键骨架,可直接贴入 MetaEditor 做语法校验。

MQL5 / C++
   class="kw">return false;
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window_in * units_count * iAgents, optimization_type, batch))
      class="kw">return false;
   iVariables = (class="type">int)window_in;
   iCount = (class="type">int)units_count;
   bTranspose = transpose;
   icIsTTP = OpenCL.AddBuffer(class="kw">sizeof(class="type">int) * Neurons(), CL_MEM_READ_WRITE);
   if(icIsTTP < class="num">0)
      class="kw">return false;
   if(!cMinDistance.AssignArray(min_distance) ||
      !cMinDistance.BufferCreate(OpenCL))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronCrossSectionalAnalysis :  class="kw">public CNeuronMVMHAttentionMLKV
   {
class="kw">protected:
   CNeuronConvOCL       cEmbeding;
   CNeuronTransposeRCDOCL  cTransposeRCD;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override;
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *prevLayer) class="kw">override;
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override;
class="kw">public:
                     CNeuronCrossSectionalAnalysis(class="type">void) {};
                    ~CNeuronCrossSectionalAnalysis(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_key, class="type">uint heads, class="type">uint heads_kv,
                        class="type">uint units_count, class="type">uint layers, class="type">uint layers_to_one_kv,
                        class="type">uint variables, ENUM_OPTIMIZATION optimization_type, class="type">uint batch) class="kw">override;
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void)  const class="kw">override   {  class="kw">return defNeuronCrossSectionalAnalysis;  }
   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="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
   class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
   };
class="type">bool CNeuronCrossSectionalAnalysis::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                                     class="type">uint window, class="type">uint window_key, class="type">uint heads, class="type">uint heads_kv,
                                     class="type">uint units_count, class="type">uint layers, class="type">uint layers_to_one_kv, class="type">uint variables,

注意力层初始化与前向传播的拼接逻辑

这段实现把跨截面分析封装在 CNeuronCrossSectionalAnalysis 里,初始化时先调父类多头注意力构造,再挂一个嵌入层和一个转置层。 初始化函数里,CNeuronMVMHAttentionMLKV::Init 的 window_key 同时作为 key 和 value 窗口传入,heads 与 heads_kv 分离,说明支持 kv 头数少于 q 头的数控稀疏注意力。 嵌入层 cEmbeding 用 GELU 激活,转置层 cTransposeRCD 做 (0,1) 轴交换,把 (variables, units_count, window_key) 的重排喂给后续注意力。任何一步 Init 或 FeedForward 返回 false 就直接中断,没有兜底。 前向传播 feedForward 严格串行:嵌入 → 转置 → 父类注意力,最后一行把转置对象指针传给 CNeuronMVMHAttentionMLKV::feedForward。想在 MT5 上验证,直接把这段塞进自定义神经网络 EA,打印各层 AsObject() 非空即可确认通路。外汇与贵金属模型训练波动剧烈,此类结构过拟合概率偏高,需以样本外窗口复核。

MQL5 / C++
                                   ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(!CNeuronMVMHAttentionMLKV::Init(numOutputs, myIndex, open_cl, window_key, window_key, heads, heads_kv,
                                     variables, layers, layers_to_one_kv, units_count, optimization_type, batch))
      class="kw">return false;
   if(!cEmbeding.Init(class="num">0, class="num">0, OpenCL, window, window, window_key, units_count, variables, optimization, iBatch))
      class="kw">return false;
   cEmbeding.SetActivationFunction(GELU);
   if(!cTransposeRCD.Init(class="num">0,class="num">1,OpenCL,variables,units_count,window_key,optimization,iBatch))
      class="kw">return false;
   SetActivationFunction(None);
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronCrossSectionalAnalysis::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cEmbeding.FeedForward(NeuronOCL))
      class="kw">return false;
   if(!cTransposeRCD.FeedForward(cEmbeding.AsObject()))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return CNeuronMVMHAttentionMLKV::feedForward(cTransposeRCD.AsObject());
  }

「收束」

MASAAT 框架靠一组交易智代并行看价格,各自跑注意力机制抓横断面和时间维度上的相关,再用时空融合把信息揉在一起,理论上能压住单模型常见的偏见。实操里我们已经在 MT5 写了多智代趋势检测和横断面注意力模块,代码侧挂了 Research.mq5、Study.mq5、Test.mq5 等七个文件,其中 NeuroNet.cl 是 OpenCL 核,跑大规模训练前先确认显卡驱动开了。 下一篇才会拿真实历史数据回测,这一版先能在 MT5 里编译通过、跑出样本收集就算落地。外汇与贵金属波动剧烈、杠杆高风险大,智代框架只是降偏见,不保证胜率。 想验证的人直接下 MQL5.zip(2222.61 KB),把 Research.mq5 丢进策略测试器选 EURUSD 日线,先看样本分布再改注意力头数。

常见问题

用局部二阶导数过零配合成交量阈值过滤,把候选点写进共享内存再归约,能避开逐根扫描的卡顿。
窗口长度按品种波动调,贵金属用短窗、外汇用长窗;斜率置信区间低于设定值就判为震荡不输出信号。
小布可加载你的分段回归输出,自动标出各段斜率冲突区并推送提醒,你只管决定是否跟单。
按品种波动率分组继承基础权重,初始化时冻结跨组连接,只让同组内部先学,后再放开。
把QK^T分块计算后即时softmax再丢弃中间矩阵,只留加权值回传,显存能压到原来的三成左右。