神经网络变得简单(第 58 部分):决策转换器(DT)·进阶篇
🧠

神经网络变得简单(第 58 部分):决策转换器(DT)·进阶篇

(2/3)· 当 GPT 思路撞上 MT5 策略,在途回报如何改写智能体动作生成逻辑

案例拆解 第 2/3 篇
多数交易者惯性以为强化学习只能按马尔可夫链一步步追过去奖励,却忽略了目标导向的序列生成。把期望结果前置为条件输入,模型也许比传统策略更贴合你的止盈框架。先理解 DT 的轨迹表示,再谈落地。

「嵌入层梯度回传的并行写法」

这段 OpenCL 内核处理的是嵌入层反向传播:每个线程先通过 do-while 累加 windows[emb] 找到当前 pos 所属的 emb 区间,定位到输出梯度和权重的偏移量 shift_out、shift_weights。 内层 for 循环把 outputs_gradient 与 weights 做 window_out 长度的点乘,得到原始 value;若 std[emb] 大于 0 则除以它做归一化,否则直接写回 inputs_gradient[pos],这一步标准差为 0 时跳过了缩放。 下方 EmbeddingUpdateWeightsAdam 内核刚拿到全局 id i 并初始化 emb=-1,就准备走同样的区间查找逻辑,配合传入的 l、b1、b2 三个标量做 Adam 更新——在 MT5 策略测试器里把 window_out 设成 16、b1=0.9、b2=0.999 跑一遍,能直接观察显存带宽是否成为瓶颈。 外汇与贵金属杠杆交易高风险,这类 GPU 内核仅用于离线训练验证,实盘信号须另行做样本外检验。

MQL5 / C++
{
  const class="type">int pos = get_global_id(class="num">0);
  class="type">int emb = -class="num">1;
  class="type">int count = class="num">0;
  do
    {
    emb++;
    count += windows[emb];
    }
  while(count <= pos);
  const class="type">int shift_out = emb * window_out;
  const class="type">int shift_weights = (pos + emb) * window_out;
  class="type">class="kw">float value = class="num">0;
  for(class="type">int i = class="num">0; i < window_out; i++)
    value += outputs_gradient[shift_out + i] * weights[shift_weights + i];
  class="type">class="kw">float s = std[emb];
  if(s > class="num">0)
    value /= s;
class=class="str">"cmt">//---
  inputs_gradient[pos] = value;
}
__kernel class="type">void EmbeddingUpdateWeightsAdam(__global class="type">class="kw">float *weights,
                                         __global const class="type">class="kw">float *gradient,
                                         __global const class="type">class="kw">float *inputs,      
                                         __global class="type">class="kw">float *matrix_m,          
                                         __global class="type">class="kw">float *matrix_v,          
                                         __global class="type">int   *windows,
                                         __global class="type">class="kw">float *std,
                                         const class="type">int window_out,
                                         const class="type">class="kw">float l,                     
                                         const class="type">class="kw">float b1,                    
                                         const class="type">class="kw">float b2                    
                                         )
  {
  const class="type">int i = get_global_id(class="num">0);
  class="type">int emb = -class="num">1;

◍ 嵌入层权重更新的 Adam 细节

在 MT5 的 OpenCL 神经网络实现里,嵌入层(Embedding)的权重更新走的是带 L1/L2 正则的 Adam 变体。下面这段循环先把索引 i 映射到具体的嵌入块与窗口偏移,再算梯度并维护一阶、二阶动量。 int count = 0; int shift = 0; do { emb++; shift = count; count += (windows[emb] + 1) * window_out; } while(count <= i); const int shift_out = emb * window_out; int shift_in = shift / window_out - emb; shift = (i - shift) / window_out; float weight = weights[i]; float g = gradient[shift_out] * inp / std[emb]; float mt = b1 * matrix_m[i] + (1 - b1) * g; float vt = b2 * matrix_v[i] + (1 - b2) * pow(g, 2); float delta = l * (mt / (sqrt(vt) + 1.0e-37f) - (l1 * sign(weight) + l2 * weight)); if(delta * g > 0) weights[i] = clamp(weights[i] + delta, -MAX_WEIGHT, MAX_WEIGHT); matrix_m[i] = mt; matrix_v[i] = vt; } 逐行看:emb 从 0 自增,直到累计 count 越过当前神经元索引 i,定位出落在第几个嵌入窗口;shift_out 是嵌入块的基础偏移,shift_in 与 shift 还原出块内相对位置。梯度 g 用 std[emb] 做标准化,避免不同窗口量纲不一致。 mt、vt 就是 Adam 的一阶和二阶动量,b1、b2 通常取 0.9 与 0.999 附近。delta 在动量商的基础上减掉 L1(sign)和 L2(weight)惩罚,学习率 l 控制步长;注意分母加了 1.0e-37f 这种极小量防除零,外汇与贵金属样本噪声大时,这种数值保护直接影响训练能否跑通。 类声明里 CNeuronEmbeddingOCL 继承自 CNeuronBaseOCL,保护成员 a_Windows[] 存各窗口长度,i_WindowOut 是输出窗口,WeightsEmbedding 与两个动量缓冲分离存储,方便在 GPU 上批量更新。想验证的话,在 MT5 策略测试器里挂一个用该类初始化的小网络,打印 weights[i] 前后差值,能直接看到 delta 符号截断(delta*g>0 才更新)带来的稀疏化效果。

MQL5 / C++
class="type">int count = class="num">0;
class="type">int shift = class="num">0;
do
  {
  emb++;
  shift = count;
  count += (windows[emb] + class="num">1) * window_out;
  }
while(count <= i);
const class="type">int shift_out = emb * window_out;
class="type">int shift_in = shift / window_out - emb;
shift = (i - shift) / window_out;
class="type">class="kw">float weight = weights[i];
class="type">class="kw">float g = gradient[shift_out] * inp / std[emb];
class="type">class="kw">float mt = b1 * matrix_m[i] + (class="num">1 - b1) * g;
class="type">class="kw">float vt = b2 * matrix_v[i] + (class="num">1 - b2) * pow(g, class="num">2);
class="type">class="kw">float delta = l * (mt / (sqrt(vt) + class="num">1.0e-37f) - (l1 * sign(weight) + l2 * weight));
if(delta * g > class="num">0)
   weights[i] = clamp(weights[i] + delta, -MAX_WEIGHT, MAX_WEIGHT);
matrix_m[i] = mt;
matrix_v[i] = vt;
}
class CNeuronEmbeddingOCL  :  class="kw">public CNeuronBaseOCL
  {
class="kw">protected:
   class="type">int               a_Windows[];
   class="type">int               i_WindowOut;
   class="type">int               i_StackSize;
   class="type">int               i_WindowsBuffer;
   class="type">int               i_STDBuffer;
   class=class="str">"cmt">//---
   CBufferFloat      WeightsEmbedding;
   CBufferFloat      FirstMomentumEmbed;
   CBufferFloat      SecondMomentumEmbed;
   class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL);
   class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL);
class="kw">public:
                     CNeuronEmbeddingOCL(class="type">void);
                    ~CNeuronEmbeddingOCL(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 stack_size, class="type">uint window_out, class="type">int &windows[]);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL);
   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=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void)      const             {  class="kw">return defNeuronEmbeddingOCL;   }
   class="kw">virtual CLayerDescription* GetLayerInfo(class="type">void);
   class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj);
   class="kw">virtual class="type">bool      Clear(class="type">void);
   };
CNeuronEmbeddingOCL::CNeuronEmbeddingOCL(class="type">void)
  {

Embedding 层的 OpenCL 初始化与权重铺排

这段 CNeuronEmbeddingOCL::Init 负责在 GPU 侧把嵌入层真正立起来。进来先卡三道门槛:open_cl 指针无效、window_out 或 stack_size 为 0、windows 数组为空,任一不满足直接返 false,避免后续在空结构上分配显存。 神经元总数按 window_out * windows.Size() * stack_size 算,比如 window_out=10、windows 含 3 个窗口、stack_size=2,就是 60 个神经元交给基类 Init 用 ADAM 优化器接管。 权重数量不是简单相乘,而是遍历 windows 累加 (windows[i]+1)*window_out,再预留空间。初始化范围用 k = 1/sqrt(weights/window_out) 做缩放,每个权重取 k*(2*GenerateWeight()-1)*WeightsMultiplier,属于带方差约束的均匀初始化,能缓解嵌入矩阵过大时的梯度失衡。 显存端先后建了 WeightsEmbedding 及一阶、二阶动量缓冲,并单独把 a_Windows 和标准差缓冲通过 AddBuffer 推到 OpenCL 设备;i_WindowsBuffer 或 i_STDBuffer 建失败就回退。宏里 def_k_Embedding 定为 59、def_k_emb_inputs 为 0,是给后续 kernel 调用的固定偏移量,改这两个数可能直接改变输入拼接布局。 销毁路径在前面析构逻辑里:ArrayFree(a_Windows) 清本地数组,若 OpenCL 有效且缓冲句柄非负就 BufferFree,最后把句柄置 INVALID_HANDLE、i_WindowOut 和 i_StackSize 复位。开 MT5 把这段贴进自定义神经元类,调一下 windows 数组长度就能看到显存占用的线性变化。

MQL5 / C++
  ArrayFree(a_Windows);
  if(!!OpenCL)
    {
      if(i_WindowsBuffer >= class="num">0)
        OpenCL.BufferFree(i_WindowsBuffer);
      if(i_STDBuffer >= class="num">0)
        OpenCL.BufferFree(i_STDBuffer);
    }
class=class="str">"cmt">//--
  i_WindowsBuffer = INVALID_HANDLE;
  i_STDBuffer = INVALID_HANDLE;
  i_WindowOut = class="num">0;
  i_StackSize = class="num">1;
}
class="type">bool CNeuronEmbeddingOCL::Init(class="type">uint numOutputs,class="type">uint myIndex,COpenCLMy *open_cl,class="type">uint stack_size, class="type">uint window_out,class="type">int &windows[])
  {
   if(CheckPointer(open_cl) == POINTER_INVALID || window_out <= class="num">0 || windows.Size() <= class="num">0 || stack_size <= class="num">0)
      class="kw">return false;
   if(!!OpenCL && OpenCL != open_cl)
      class="kw">delete OpenCL;
   class="type">uint numNeurons = window_out * windows.Size() * stack_size;
   if(!CNeuronBaseOCL::Init(numOutputs,myIndex,open_cl,numNeurons,ADAM,class="num">1))
      class="kw">return false;
   class="type">uint weights = class="num">0;
   ArrayCopy(a_Windows,windows);
   i_WindowOut = (class="type">int)window_out;
   i_StackSize = (class="type">int)stack_size;
   for(class="type">uint i = class="num">0; i < windows.Size(); i++)
      weights += (windows[i] + class="num">1) * window_out;
   if(!WeightsEmbedding.Reserve(weights))
      class="kw">return false;
   class="type">class="kw">float k = class="num">1.0f / sqrt((class="type">class="kw">float)weights / (class="type">class="kw">float)window_out);
   for(class="type">uint i = class="num">0; i < weights; i++)
      if(!WeightsEmbedding.Add(k * (class="num">2 * GenerateWeight() - class="num">1.0f)*WeightsMultiplier))
         class="kw">return false;
   if(!WeightsEmbedding.BufferCreate(OpenCL))
      class="kw">return false;
   if(!FirstMomentumEmbed.BufferInit(weights, class="num">0))
      class="kw">return false;
   if(!FirstMomentumEmbed.BufferCreate(OpenCL))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!SecondMomentumEmbed.BufferInit(weights, class="num">0))
      class="kw">return false;
   if(!SecondMomentumEmbed.BufferCreate(OpenCL))
      class="kw">return false;
   i_WindowsBuffer = OpenCL.AddBuffer(class="kw">sizeof(class="type">int) * a_Windows.Size(),CL_MEM_READ_WRITE);
   if(i_WindowsBuffer < class="num">0 || !OpenCL.BufferWrite(i_WindowsBuffer,a_Windows,class="num">0,class="num">0,a_Windows.Size()))
      class="kw">return false;
   i_STDBuffer = OpenCL.AddBuffer(class="kw">sizeof(class="type">class="kw">float) * a_Windows.Size(),CL_MEM_READ_WRITE);
   if(i_STDBuffer<class="num">0)
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="macro">#define def_k_Embedding                class="num">59
class="macro">#define def_k_emb_inputs               class="num">0

「Embedding 层的前向传参怎么绑 OpenCL 缓冲」

在 MT5 用 OpenCL 加速神经网络时,Embedding 层的前向计算靠一组宏常量来定位内核参数槽位。上面代码里 def_k_Embedding 主核用了 5 个参数索引:输入缓冲 0、输出缓冲 1、权重 2、窗口 3、标准差缓冲 4,另外 def_k_EmbeddingHiddenGradient(60)和 def_k_EmbeddingUpdateWeightsAdam(61)分别管反向梯度与 Adam 权重更新,后者一次要传 11 个参(权重、梯度、输入、m 矩阵、v 矩阵、窗口、std、输出窗口、学习率、b1、b2)。 CNeuronEmbeddingOCL::feedForward 里做的第一件事,是把上游神经元的输出索引绑到内核的输入参数槽。若 NeuronOCL 或 OpenCL 句柄为空直接返 false,这是避免空指针进 GPU 核的典型防御。 随后连续三次 SetArgumentBuffer:分别把输入缓冲(def_k_emb_inputs)、本层输出缓冲(def_k_emb_outputs)、以及标准差缓冲(def_k_emb_std,对应 i_STDBuffer)推给 def_k_Embedding 内核。任何一次绑定失败就 printf 打出函数名、GetLastError 和 __LINE__ 然后返 false——在 MT5 策略测试器里这类报错能直接定位到是哪一行 OpenCL 参数没挂上。 开 MT5 验证时,若你自建 Embedding 类却在前向阶段黑屏无输出,优先查这三个 SetArgumentBuffer 的返回值;外汇与贵金属品种上跑这类 GPU 网络仍属高风险,显存溢出或驱动不匹配都可能让推断静默失败。

MQL5 / C++
class="macro">#define def_k_emb_outputs                class="num">1
class="macro">#define def_k_emb_weights                class="num">2
class="macro">#define def_k_emb_windows                class="num">3
class="macro">#define def_k_emb_std                    class="num">4
class="macro">#define def_k_emb_stack_size             class="num">5
class=class="str">"cmt">//---
class="macro">#define def_k_EmbeddingHiddenGradient   class="num">60
class="macro">#define def_k_ehg_inputs_gradient        class="num">0
class="macro">#define def_k_ehg_outputs_gradient       class="num">1
class="macro">#define def_k_ehg_weights                class="num">2
class="macro">#define def_k_ehg_windows                class="num">3
class="macro">#define def_k_ehg_std                    class="num">4
class="macro">#define def_k_ehg_window_out             class="num">5
class=class="str">"cmt">//---
class="macro">#define def_k_EmbeddingUpdateWeightsAdam   class="num">61
class="macro">#define def_k_euw_weights                class="num">0
class="macro">#define def_k_euw_gradient               class="num">1
class="macro">#define def_k_euw_inputs                 class="num">2
class="macro">#define def_k_euw_matrix_m               class="num">3
class="macro">#define def_k_euw_matrix_v               class="num">4
class="macro">#define def_k_euw_windows                class="num">5
class="macro">#define def_k_euw_std                    class="num">6
class="macro">#define def_k_euw_window_out             class="num">7
class="macro">#define def_k_euw_learning_rate          class="num">8
class="macro">#define def_k_euw_b1                     class="num">9
class="macro">#define def_k_euw_b2                     class="num">10
class="type">bool CNeuronEmbeddingOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL || !OpenCL)
      class="kw">return false;
   if(!OpenCL.SetArgumentBuffer(def_k_Embedding, def_k_emb_inputs, NeuronOCL.getOutputIndex()))
     {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
     }
   if(!OpenCL.SetArgumentBuffer(def_k_Embedding, def_k_emb_outputs, getOutputIndex()))
     {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
     }
   if(!OpenCL.SetArgumentBuffer(def_k_Embedding, def_k_emb_std, i_STDBuffer))
     {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
     }

◍ Embedding 核的参数绑定与层描述结构

在 GPU 上跑 Embedding 层,先得把权重矩阵、窗口缓冲和栈长度塞进 OpenCL 核的参数槽。任何一次 SetArgumentBuffer 或 SetArgument 失败都直接返回 false,并在终端打印函数名、错误码和行号,方便在 MT5 里定位是哪一根线没接上。 核启动用二维工作项:global_work_size 设为 {i_WindowOut, a_Windows.Size()},local_work_size 设为 {i_WindowOut, 1}。这意味着每个输出窗口由一条包含 i_WindowOut 个线程的一维组处理,窗口数量由第二个维度摊开。若 Execute 返回失败,同样走 false 分支并报错。 Clear 方法负责把 Output 缓冲清零(BufferInit 到 0),若 OpenCL 上下文不存在则直接返回 true 跳过写回。这一轻量复位逻辑在反复训练迭代中能避免旧梯度污染。 CLayerDescription 类用普通字段描述一层网络:type 标神经元类型,count 是神经元数,window 与 window_out 分别是输入输出窗口尺寸,step 为滑动步长,layers 和 batch 控制堆叠与批大小,activation 与 optimization 枚举选定激活函数和优化器。把这些字段在代码里打印出来,就能在策略加载时核对网络拓扑是否符合预期。

MQL5 / C++
  if(!OpenCL.SetArgumentBuffer(def_k_Embedding, def_k_emb_weights, WeightsEmbedding.GetIndex()))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
    }
  if(!OpenCL.SetArgumentBuffer(def_k_Embedding, def_k_emb_windows, i_WindowsBuffer))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
    }
  if(!OpenCL.SetArgument(def_k_Embedding, def_k_emb_stack_size, i_StackSize))
    {
      printf("Error of set parameter kernel %s: %d; line %d", __FUNCTION__,GetLastError(), __LINE__);
      class="kw">return false;
    }
  class="type">uint global_work_offset[class="num">2] = {class="num">0,class="num">0};
  class="type">uint global_work_size[class="num">2]   = {i_WindowOut,a_Windows.Size()};
  class="type">uint local_work_size[class="num">2]    = {i_WindowOut,class="num">1};
  if(!OpenCL.Execute(def_k_Embedding, class="num">2, global_work_offset, global_work_size,local_work_size))
    {
      printf("Error of execution kernel %s: %d", __FUNCTION__,GetLastError());
      class="kw">return false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronEmbeddingOCL::Clear(class="type">void)
  {
   if(!Output.BufferInit(Output.Total(),class="num">0))
      class="kw">return false;
   if(!OpenCL)
      class="kw">return true;
class=class="str">"cmt">//---
   class="kw">return Output.BufferWrite();
  }
class CLayerDescription    :  class="kw">public CObject
  {
class="kw">public:
   class=class="str">"cmt">/** Constructor */                    CLayerDescription(class="type">void);
   class=class="str">"cmt">/** Destructor */~CLayerDescription(class="type">void) {};
   class=class="str">"cmt">//---
   class="type">int                type;            class=class="str">"cmt">///< Type of neurons in layer(\ref ObjectTypes)
   class="type">int                count;           class=class="str">"cmt">///< Number of neurons
   class="type">int                window;          class=class="str">"cmt">///< Size of class="kw">input window
   class="type">int                window_out;      class=class="str">"cmt">///< Size of output window
   class="type">int                step;            class=class="str">"cmt">///< Step size
   class="type">int                layers;          class=class="str">"cmt">///< Layers count
   class="type">int                batch;           class=class="str">"cmt">///< Batch Size
   ENUM_ACTIVATION    activation;      class=class="str">"cmt">///< Type of activation function(class="macro">#ENUM_ACTIVATION)
   ENUM_OPTIMIZATION  optimization;    class=class="str">"cmt">///< Type of optimization method(class="macro">#ENUM_OPTIMIZATION)

用代码堆出五层强化学习网络骨架

在 MT5 里搭一个能跑价格行为的智能体,第一步不是调参,而是把网络层描述对象塞进 CArrayObj。下面这段 CreateDescriptions 函数直接给出了五层结构,从输入到稀疏注意力全用 CLayerDescription 实例声明,复制进 EA 的神经网络模块就能编译验证。 输入层用 defNeuronBaseOCL,节点数由 NRewards + BarDescr*NBarInPattern + AccountDescr + TimeDescription + NActions 动态算出来,优化器选 ADAM、激活函数 None,相当于把账户状态、K线描述和动作空间一次性送进网络。 第二层接 BatchNorm(defNeuronBatchNormOCL),batch 写死 1000,用来稳初始分布;第三层 Embedding 把 HistoryBars 根 K 线压成 EmbeddingSize 维向量,windows 数组按 {BarDescr*NBarInPattern, AccountDescr, TimeDescription, NRewards, NActions} 切分特征通道。 第四层是重点:defNeuronMLMHSparseAttentionOCL 做多头稀疏注意力,step=4、window_out=16、layers=4,dropout 概率用 Sparse 常量控制,外汇与贵金属行情噪声大,这种稀疏结构可能降低过拟合概率。第五层回到全连接(defNeuronBaseOCL),LatentCount 个节点配 LReLU,留作策略隐变量。 把这段逻辑跑起来后,你只需改 HistoryBars 和 EmbeddingSize 两个量,就能观察 Agent 在 XAUUSD 15M 上信号频率的变化,高风险品种建议先开模拟盘验证。

MQL5 / C++
  class="type">class="kw">float                probability;   class=class="str">"cmt">///< Probability of neurons shutdown, only Dropout used
  class="type">int                 windows[];
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool        Copy(CLayerDescription *source);
  class=class="str">"cmt">//---
  class="kw">virtual class="type">bool        class="kw">operator= (CLayerDescription *source)  { class="kw">return Copy(source); }
  };
class="type">bool CreateDescriptions(CArrayObj *agent)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!agent)
     {
      agent = new CArrayObj();
      if(!agent)
         class="kw">return false;
     }
class=class="str">"cmt">//--- Agent
   agent.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 = (NRewards + BarDescr*NBarInPattern + AccountDescr + TimeDescription + NActions);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!agent.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">1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!agent.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 = defNeuronEmbeddingOCL;
   prev_count = descr.count = HistoryBars;
     {
      class="type">int temp[] = {BarDescr*NBarInPattern,AccountDescr,TimeDescription,NRewards,NActions};
      ArrayCopy(descr.windows,temp);
     }
   class="type">int prev_wout = descr.window_out = EmbeddingSize;
   if(!agent.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronMLMHSparseAttentionOCL;
   descr.count = prev_count;
   descr.window = prev_wout;
   descr.step = class="num">4;
   descr.window_out = class="num">16;
   descr.layers = class="num">4;
   descr.probability = Sparse;
   descr.optimization = ADAM;
   if(!agent.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 = LReLU;
   if(!agent.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">5
把回测轨迹交给小布盯盘跑
这些 DT 轨迹诊断与小布盯盘的 AIGC 已内置,打开对应品种页即可看到在途回报分布,你专注调参和决策。

常见问题

Q-learning 依赖状态到动作的价值映射且假设马尔可夫性,DT 把问题转成依未来期望奖励自回归生成动作序列,更偏目标条件策略,外汇贵金属高风险下需防过拟合。
目前小布盯盘内置的是轨迹诊断与在途回报可视化,模型训练仍要在 MT5 终端完成,训练后把权重接入观察页即可。
达到目标总奖励后若继续生成,剩余期望回报转负会扰动自回归条件,模型可能输出背离原策略的动作,概率上增加回撤。
K 过小会切断长周期价格结构记忆,复杂复合环境里重要转折形态可能不被提取,倾向用验证集测不同 K 的夏普衰减。