神经网络变得简单(第 88 部分):时间序列密集编码器(TiDE)·进阶篇
🧠

神经网络变得简单(第 88 部分):时间序列密集编码器(TiDE)·进阶篇

(2/3)·当线性模型暴露短板、变换器算力吃紧,TiDE如何用纯MLP实现5倍提速与更优预测

案例拆解新手友好 第 2/3 篇

不少交易者把变换器当成时间序列预测的唯一答案,却忽略了它在长周期推演里算力陡增、收益却趋平的现实。另一些人迷信线性模型,直到协变量出现非线性关系时账户回测直接失真。这篇我们拆开TiDE,看它怎么用最朴素的MLP绕开这两类坑。

◍ TiDE 前向与前向梯度的 OpenCL 实现骨架

在 MT5 里用 OpenCL 跑 TiDE 类时序模型,前向传播的逻辑是先对齐辅助输入缓冲,再逐层 FeedForward。代码里先用 getOutputIndex 判断第二个输入是否落在预期索引,不一致就通过 BufferSet 重绑,否则直接返回 false 中断计算。 前向主链路把全局残差、两组特征投影、历史拼接缓冲依次喂入,再用 acEncoderDecoder 数组做循环前向:prev 指针从编码器输入起,每轮更新为当前层对象,total 即数组 Size(),任一层失败立即退出。最后用 Concat 把解码器输入拼好,经 TemporalDecoder 输出后与残差做 SumAndNormilize,Output 长度由 iForecast 控制。 反向的 calcInputGradients 则是对称拆解:先从 TemporalDecoder 反传梯度,DeConcat 把末层编码器-解码器梯度按 iForecast 与 iVariables 切回特征投影与时间解码输入。循环从 total-2 倒序到 0,逐层 calcHiddenGradients,再 DeConcat 回历史输入与编码器输入。 注意 cGlobalResidual 和 NeuronOCL 的激活态:若 Activation 不是 None,必须调 DeActivation 对输出与梯度做对应反激活。外汇与贵金属行情下用这类 GPU 前向网络预测,误差可能放大,实盘前务必在策略测试器用历史数据校验缓冲维度。

MQL5 / C++
if(cFeatureInput.getOutputIndex() != SecondInput.GetIndex())
  {
    CBufferFloat *temp = cFeatureInput.getOutput();
    if(!temp.BufferSet(SecondInput.GetIndex()))
      class="kw">return false;
  }
if(!cGlobalResidual.FeedForward(NeuronOCL))
    class="kw">return false;
if(!acFeatureProjection[class="num">0].FeedForward(NeuronOCL))
    class="kw">return false;
if(!acFeatureProjection[class="num">1].FeedForward(cFeatureInput.AsObject()))
    class="kw">return false;
if(!Concat(NeuronOCL.getOutput(), acFeatureProjection[class="num">0].getOutput(),
cEncoderInput.getOutput(), iHistory, iHistory, iVariables))
    class="kw">return false;
class="type">uint total = acEncoderDecoder.Size();
CNeuronBaseOCL *prev = cEncoderInput.AsObject();
for(class="type">uint i = class="num">0; i < total; i++)
  {
    if(!acEncoderDecoder[i].FeedForward(prev))
      class="kw">return false;
    prev = acEncoderDecoder[i].AsObject();
  }
if(!Concat(prev.getOutput(), acFeatureProjection[class="num">1].getOutput(),
cTemporalDecoderInput.getOutput(), iForecast, iForecast, iVariables))
    class="kw">return false;
if(!cTemporalDecoder.FeedForward(cTemporalDecoderInput.AsObject()))
    class="kw">return false;
if(!SumAndNormilize(cGlobalResidual.getOutput(), cTemporalDecoder.getOutput(), Output,
iForecast, true))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronTiDEOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
  if(!cTemporalDecoderInput.calcHiddenGradients(cTemporalDecoder.AsObject()))
    class="kw">return false;
  class="type">int total = (class="type">int)acEncoderDecoder.Size();
  if(!DeConcat(acEncoderDecoder[total - class="num">1].getGradient(), acFeatureProjection[class="num">1].getGradient(),
  cTemporalDecoderInput.getGradient(), iForecast, iForecast, iVariables))
    class="kw">return false;
  for(class="type">int i = total - class="num">2; i >= class="num">0; i--)
    if(!acEncoderDecoder[i].calcHiddenGradients(acEncoderDecoder[i + class="num">1].AsObject()))
      class="kw">return false;
  if(!cEncoderInput.calcHiddenGradients(acEncoderDecoder[class="num">0].AsObject()))
    class="kw">return false;
  if(!DeConcat(cHistoryInput.getGradient(), acFeatureProjection[class="num">0].getGradient(),
  cEncoderInput.getGradient(), iHistory, iHistory, iVariables))
    class="kw">return false;
  if(cGlobalResidual.Activation() != None)
    {
      if(!DeActivation(cGlobalResidual.getOutput(), cGlobalResidual.getGradient(),
      cGlobalResidual.getGradient(), cGlobalResidual.Activation()))
        class="kw">return false;
    }
  if(!NeuronOCL.calcHiddenGradients(cGlobalResidual.AsObject()))
    class="kw">return false;
  if(NeuronOCL.Activation()!=None)
    if(!DeActivation(cHistoryInput.getOutput(),cHistoryInput.getGradient(),
    cHistoryInput.getGradient(),SecondActivation))
      class="kw">return false;

「TiDE 反向传播与权重落盘的实现细节」

这段 CNeuronTiDEOCL 的成员函数展示了 TiDE 模型在 OpenCL 后端做梯度回传与权重更新的关键链路。反向阶段先对历史输入分支和特征投影分支分别做 SumAndNormilize 与 calcHiddenGradients,其中第二次归一化使用的缩放系数是 0.5f,而历史梯度归一化系数为 1,这种不对称缩放会影响残差与特征通道的梯度幅值平衡。 更新权重时,代码遍历 acEncoderDecoder 数组,用 prev 指针串联各层 UpdateInputWeights,循环次数由 total = acEncoderDecoder.Size() 决定;若任何一层返回 false 则整个函数提前退出,说明单层更新失败会阻断后续时序解码器的权重同步。 Save 函数把 iHistory、iForecast、iVariables、iFeatures 四个 uint 维度以 INT_VALUE 长度写入文件句柄,任一 FileWriteInteger 返回值小于 INT_VALUE 即判失败。在 MT5 里跑这套网络时,可故意让某层 UpdateInputWeights 返回 false 观察训练中断点,验证梯度链路是否如预期般脆弱。

MQL5 / C++
if(!SumAndNormilize(NeuronOCL.getGradient(), cHistoryInput.getGradient(),
NeuronOCL.getGradient(), iHistory, false, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
 if(!cFeatureInput.calcHiddenGradients(acFeatureProjection[class="num">0].AsObject()))
   class="kw">return false;
 if(!SumAndNormilize(cFeatureInput.getGradient(), cFeatureInput.getGradient(), SecondGradient,
 iFeatures, false, class="num">0, class="num">0, class="num">0, class="num">0.5f))
   class="kw">return false;
 if(!cFeatureInput.calcHiddenGradients(acFeatureProjection[class="num">1].AsObject()))
   class="kw">return false;
 if(!SumAndNormilize(SecondGradient, cFeatureInput.getGradient(), SecondGradient, iFeatures,
                                                                          false, class="num">0, class="num">0, class="num">0, class="num">1.0f))
   class="kw">return false;
 if(SecondActivation!=None)
   if(!DeActivation(SecondInput,SecondGradient,SecondGradient,SecondActivation))
     class="kw">return false;
class=class="str">"cmt">//---
 class="kw">return true;
 }
class="type">bool CNeuronTiDEOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
class=class="str">"cmt">//---
  if(!cGlobalResidual.UpdateInputWeights(cHistoryInput.AsObject()))
    class="kw">return false;
  if(!acFeatureProjection[class="num">0].UpdateInputWeights(cHistoryInput.AsObject()))
    class="kw">return false;
  if(!acFeatureProjection[class="num">1].UpdateInputWeights(cFeatureInput.AsObject()))
    class="kw">return false;
class=class="str">"cmt">//---
  class="type">uint total = acEncoderDecoder.Size();
  CNeuronBaseOCL *prev = cEncoderInput.AsObject();
  for(class="type">uint i = class="num">0; i < total; i++)
    {
     if(!acEncoderDecoder[i].UpdateInputWeights(prev))
       class="kw">return false;
     prev = acEncoderDecoder[i].AsObject();
    }
class=class="str">"cmt">//---
  if(!cTemporalDecoder.UpdateInputWeights(cTemporalDecoderInput.AsObject()))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronTiDEOCL::Save(const class="type">int file_handle)
  {
  if(!CNeuronBaseOCL::Save(file_handle))
    class="kw">return false;
class=class="str">"cmt">//---
  if(FileWriteInteger(file_handle, (class="type">int)iHistory, INT_VALUE) < INT_VALUE)
    class="kw">return false;
  if(FileWriteInteger(file_handle, (class="type">int)iForecast, INT_VALUE) < INT_VALUE)
    class="kw">return false;
  if(FileWriteInteger(file_handle, (class="type">int)iVariables, INT_VALUE) < INT_VALUE)
    class="kw">return false;
  if(FileWriteInteger(file_handle, (class="type">int)iFeatures, INT_VALUE) < INT_VALUE)
    class="kw">return false;
class=class="str">"cmt">//---

TiDE 模型的存档与重载路径

把训练好的 TiDE 网络落盘,靠的是 Save 里先写编码器层数、再逐层序列化。代码先取 acEncoderDecoder.Size() 得到 total,用 FileWriteInteger 以 INT_VALUE 长度写入文件头,任何一次写不足 INT_VALUE 就直接 return false,保证文件结构不被截断。 随后对 acEncoderDecoder 每个元素调 Save,再依次保存 cGlobalResidual、两个 acFeatureProjection 与 cTemporalDecoder,循环里固定 i<2 说明特征投影层写死为两层,改结构得动这块硬编码。 Load 方向更苛刻:每读一个整型参数(iHistory、iForecast、iVariables、iFeatures、total)都用 FileIsEnding 判尾,读到文件尾立刻返回 false,避免旧模型文件缺字段导致数组越界。 若 prev_size 与读到的 total 不等,用 ArrayResize 扩容;新扩出的层走 Init(0, i+2, OpenCL, 1,1,1, ADAM, 1) 初始化,老层只调 LoadInsideLayer 灌权重。开 MT5 把这段接进自己的 CNeuronTiDEOCL,能直接验证模型热重载是否丢参。

MQL5 / C++
class="type">uint total = acEncoderDecoder.Size();
if(FileWriteInteger(file_handle, (class="type">int)total, INT_VALUE) < INT_VALUE)
   class="kw">return false;
for(class="type">uint i = class="num">0; i < total; i++)
   if(!acEncoderDecoder[i].Save(file_handle))
      class="kw">return false;
if(!cGlobalResidual.Save(file_handle))
   class="kw">return false;
for(class="type">int i = class="num">0; i < class="num">2; i++)
   if(!acFeatureProjection[i].Save(file_handle))
      class="kw">return false;
if(!cTemporalDecoder.Save(file_handle))
   class="kw">return false;
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CNeuronTiDEOCL::Load(const class="type">int file_handle)
  {
  if(!CNeuronBaseOCL::Load(file_handle))
     class="kw">return false;
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iHistory = (class="type">uint)FileReadInteger(file_handle);
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iForecast = (class="type">uint)FileReadInteger(file_handle);
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iVariables = (class="type">uint)FileReadInteger(file_handle);
  if(FileIsEnding(file_handle))
     class="kw">return false;
  iFeatures = (class="type">uint)FileReadInteger(file_handle);
  if(FileIsEnding(file_handle))
     class="kw">return false;
  class="type">int total = FileReadInteger(file_handle);
  class="type">int prev_size = (class="type">int)acEncoderDecoder.Size();
  if(prev_size != total)
     if(ArrayResize(acEncoderDecoder, total) < total)
        class="kw">return false;
  for(class="type">int i = class="num">0; i < total; i++)
     {
     if(i >= prev_size)
        if(!acEncoderDecoder[i].Init(class="num">0, i + class="num">2, OpenCL, class="num">1, class="num">1, class="num">1, ADAM, class="num">1))
           class="kw">return false;
     if(!LoadInsideLayer(file_handle, acEncoderDecoder[i].AsObject()))
        class="kw">return false;
     }
  if(!LoadInsideLayer(file_handle, cGlobalResidual.AsObject()))
     class="kw">return false;
  for(class="type">int i = class="num">0; i < class="num">2; i++)
     if(!LoadInsideLayer(file_handle, acFeatureProjection[i].AsObject()))
        class="kw">return false;
  if(!LoadInsideLayer(file_handle, cTemporalDecoder.AsObject()))
     class="kw">return false;

◍ 编码器各层的初始化与张量排布

在 MT5 里搭 TiDE 类时序模型时,编码器的描述对象必须先做空指针兜底:若传入的 CArrayObj *encoder 为空,就 new 一个再继续,否则后面 Add _layer 会直接崩在终端日志里。 输入层用 defNeuronBaseOCL,节点数等于 HistoryBars * BarDescr,激活函数设 None、优化器走 ADAM;prev_count 被存下来,供后续 BatchNorm 层复用同样宽度。 第一层接 defNeuronBatchNormOCL,batch 参数写死 1000,这意味着训练时按每批 1000 条样本做归一化——如果你的样本数远小于此,显存占用会虚高,可在回测前调小。 第二层 defNeuronTransposeOCL 把 (HistoryBars, BarDescr) 转置,count=HistoryBars、window=BarDescr;第三层 defNeuronTiDEOCL 才是核心,window_out=NForecast、step=4,windows 数组按 [HistoryBars, 2*EmbeddingSize, EmbeddingSize, 2*EmbeddingSize, NForecast] 排好,任何一层 Add 失败都 delete descr 并返 false,避免野指针。 外汇与贵金属波动大、杠杆高,这套结构只是特征抽取前端,实盘信号须结合风控,模型输出仅代表价格走势的概率倾向。

MQL5 / C++
  if(!cHistoryInput.Init(class="num">0, total + class="num">5, OpenCL, iHistory * iVariables, optimization, iBatch))
      class="kw">return false;
  if(!cFeatureInput.Init(class="num">0, total + class="num">6, OpenCL, iFeatures, optimization, iBatch))
      class="kw">return false;
  if(!cEncoderInput.Init(class="num">0, total + class="num">7, OpenCL, class="num">2 * iHistory * iVariables, optimization,iBatch))
      class="kw">return false;
  if(!cTemporalDecoderInput.Init(class="num">0, total + class="num">8, OpenCL, class="num">2 * iForecast * iVariables,optimization,
iBatch))
      class="kw">return false;
  if(cGlobalResidual.getGradient() != Gradient)
    if(!cGlobalResidual.SetGradient(Gradient))
        class="kw">return false;
  if(cTemporalDecoder.getGradient() != getGradient())
    if(!cTemporalDecoder.SetGradient(Gradient))
        class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CreateEncoderDescriptions(CArrayObj *encoder)
  {
class=class="str">"cmt">//---
  CLayerDescription *descr;
class=class="str">"cmt">//---
  if(!encoder)
    {
      encoder = new CArrayObj();
      if(!encoder)
        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">1000;
  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 = defNeuronTransposeOCL;
  descr.count = HistoryBars;
  descr.window = BarDescr;
  if(!encoder.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 = defNeuronTiDEOCL;
  descr.count = BarDescr;
  descr.window = HistoryBars;
  descr.window_out = NForecast;
  descr.step = class="num">4;
  {
    class="type">int windows[]={HistoryBars,class="num">2*EmbeddingSize,EmbeddingSize,class="num">2*EmbeddingSize,NForecast};
    if(ArrayCopy(descr.windows,windows)<=class="num">0)
      class="kw">return false;
  }
  descr.activation = None;
  if(!encoder.Add(descr))

「编码器尾层与 Actor/Critic 交叉注意力堆叠」

编码器后半段从第四层开始收口:第四层用 defNeuronConvOCL,窗口与步长都设为 NForecast,输出维度与 BarDescr 对齐;第五层做转置卷积 defNeuronTransposeOCL 把时序特征还原;第六层接 defNeuronRevInDenormOCL 做反归一化,优化器指定 ADAM,layers=1。任何一层 Add 失败都 delete descr 并返回 false,保证对象不泄漏。 LatentLayer 宏写死为 4,意味着潜变量取编码器第四层输出,后续 Actor/Critic 都以此为条件输入。 Actor 与 Critic 各自用 for 循环连续压 10 层 defNeuronCrossAttenOCL(layer 2-12)。每层 units 拷 {1, BarDescr}、windows 拷 {EmbeddingSize, NForecast},window_out=32、step=4、激活 None、优化 ADAM。10 层堆叠后模型可能对长跨度的外汇/贵金属 tick 序列学到交叉依赖,但过拟合概率随层数上升,MT5 上跑前先把 NForecast 调小做 sanity check 更稳。 外汇与贵金属杠杆高、滑点跳空频繁,这类深度结构仅作概率性辅助,实盘前务必用历史数据回测验证。

MQL5 / C++
  {
      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 = defNeuronConvOCL;
   descr.count = BarDescr;
   descr.window = NForecast;
   descr.step = NForecast;
   descr.window_out = NForecast;
   descr.activation=None;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronTransposeOCL;
   descr.count = BarDescr;
   descr.window = NForecast;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">6
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = BarDescr*NForecast;
   descr.activation = None;
   descr.optimization = ADAM;
   descr.layers = class="num">1;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="macro">#define           LatentLayer               class="num">4
class="type">bool CreateDescriptions(CArrayObj *actor, CArrayObj *critic)
  {
class=class="str">"cmt">//---
........
........
class=class="str">"cmt">//--- Actor
........
........
class=class="str">"cmt">//--- layer class="num">2-class="num">12
   for(class="type">int i = class="num">0; i < class="num">10; i++)
     {
      if(!(descr = new CLayerDescription()))
         class="kw">return false;
      descr.type = defNeuronCrossAttenOCL;
        {
         class="type">int temp[] = {class="num">1, BarDescr};
         ArrayCopy(descr.units, temp);
        }
        {
         class="type">int temp[] = {EmbeddingSize, NForecast};
         ArrayCopy(descr.windows, temp);
        }
      descr.window_out = class="num">32;
      descr.step = class="num">4;
      descr.activation = None;
      descr.optimization = ADAM;
      if(!actor.Add(descr))
        {
         class="kw">delete descr;
         class="kw">return false;
        }
     }
........
........
class=class="str">"cmt">//--- Critic
........
........
class=class="str">"cmt">//--- layer class="num">2-class="num">12
   for(class="type">int i = class="num">0; i < class="num">10; i++)
     {
      if(!(descr = new CLayerDescription()))
         class="kw">return false;
      descr.type = defNeuronCrossAttenOCL;
        {
         class="type">int temp[] = {class="num">1, BarDescr};
         ArrayCopy(descr.units, temp);
        }
        {

训练循环里的时间特征注入

Critic 网络描述符在 Add 之前,先把窗口尺寸写进 windows 数组:EmbeddingSize 与 NForecast 两个值,随后 window_out 固定为 32、step 为 4,激活函数留 None,优化器走 ADAM。若 Add 返回失败,直接 delete 描述符并 return false,避免悬空对象。 Train 函数从 GetProbTrajectories(Buffer, 0.9) 拿到概率向量后进入迭代。每次先用 SampleTrajectory 抽一条轨迹,再用 MathRand 平方分布把起点 i 压在 Buffer[tr].Total - 2 - NForecast 范围内;i<=0 时 iter 自减并 continue,防止越界。 时间特征用四个周期分量拼出来:年周期用 2024.01.01 减 2023.01.01 作分母取 sin,月线 PERIOD_MN1 取 cos,周线 PERIOD_W1 与日线 PERIOD_D1 取 sin,全部以 2πx 为相位。bTime 写满后 BufferWrite,再喂给 Encoder.feedForward 做状态编码。外汇与贵金属行情受周期扰动明显,这类特征可能提升模型对季节波动的捕捉概率,但回测不代表实盘,杠杆品种高风险。

MQL5 / C++
class="type">int temp[] = {EmbeddingSize, NForecast};
ArrayCopy(descr.windows, temp);
}
descr.window_out = class="num">32;
descr.step = class="num">4;
descr.activation = None;
descr.optimization = ADAM;
if(!critic.Add(descr))
  {
   class="kw">delete descr;
   class="kw">return false;
  }
 }
........
........
class=class="str">"cmt">//---
 class="kw">return true;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Train function                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Train(class="type">void)
  {
class=class="str">"cmt">//---
  vector<class="type">class="kw">float> probability = GetProbTrajectories(Buffer, class="num">0.9);
class=class="str">"cmt">//---
  vector<class="type">class="kw">float> result, target;
  class="type">bool Stop = false;
class=class="str">"cmt">//---
  class="type">uint ticks = GetTickCount();
  for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter ++)
    {
     class="type">int tr = SampleTrajectory(probability);
     class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) *
(Buffer[tr].Total - class="num">2 - NForecast));
     if(i <= class="num">0)
       {
        iter--;
        class="kw">continue;
       }
     bState.AssignArray(Buffer[tr].States[i].state);
     bTime.Clear();
     class="type">class="kw">double time = (class="type">class="kw">double)Buffer[tr].States[i].account[class="num">7];
     class="type">class="kw">double x = time / (class="type">class="kw">double)(D&class="macro">#x27;class="num">2024.01.class="num">01&class="macro">#x27; - D&class="macro">#x27;class="num">2023.01.class="num">01&class="macro">#x27;);
     bTime.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
     x = time / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1);
     bTime.Add((class="type">class="kw">float)MathCos(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
     x = time / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
     bTime.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
     x = time / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
     bTime.Add((class="type">class="kw">float)MathSin(x != class="num">0 ? class="num">2.0 * M_PI * x : class="num">0));
     if(bTime.GetIndex() >= class="num">0)
       bTime.BufferWrite();
     class=class="str">"cmt">//--- State Encoder
     if(!Encoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, false,
把重复劳动交给小布
TiDE的编码解码结构里大量矩阵映射与协变量拼接是固定动作,这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到特征通道的实时重构,你只管判断信号可信度。

常见问题

论文模拟显示线性类模型在LDS设定下误差近最优,TiDE加入MLP非线性后相较LSTM与变换器有概率优势,但实盘外汇贵金属属高风险,需自测品种波动结构。
它靠编码器MLP把历史序列与协变量压成稠密向量,解码器再展平到预测窗,计算随上下文长度线性扩张,不像变换器那样平方级爆炸。
纯线性模型难建模非线性协变关系,TiDE的隐藏层ReLU加跳接引入了受控非线性,倾向在复杂协变下保住精度。
目前小布内置的是特征通道重构与诊断视图,模型训练仍建议在MT5端完成,把重复劳动交给小布,你专注决策。
基准测试基于公开数据集,实盘tick级重采样后速度增益可能浮动,但架构无递归卷积,横向扩展成本明显低于变换器系。