交易中的神经网络:面向自适应智能体行为的技能层次结构(完结篇)·进阶篇
🧠

交易中的神经网络:面向自适应智能体行为的技能层次结构(完结篇)·进阶篇

(2/3)· 接上篇通用技能编码器,本篇把控制器对象、训练与回测一次跑通

含代码示例偏理论 第 2/3 篇
很多人在多智能体交易实验里只训好编码器就急着上实盘,忽略了控制器里本地观测与双层技能的耦合。一旦市场状态切太快,孤立模块会互相拖后腿。先把对象边界划清,再谈自适应。

◍ 四模型分工与架构落地

整套可训练结构里实际跑了四个网络:环境状态编码器当规划器用,以监督学习方式从原始观测里抽出通用技能,并在指定规划时域内往前推演环境状态——这比原版 HiSSD 只做单步预测更适合外汇里「开仓后长期持有」的打法。控制器复用编码器的前两层,产出各智能体的动作张量;管理器(Actor)吃账户状态向量,经交叉注意力对齐候选动作后由三层全连接给出是否下单的判定;第四个网络拿编码器隐层的通用技能做输入,用两层隐藏层加 Sigmoid 吐出价格运行方向的概率,偏多偏空只看这个 0~1 的输出。 所有架构都塞进 CreateDescriptions 方法,它接四个动态数组指针,先逐個做空指针校验,缺了就 new 一个 CArrayObj,任何一步失败直接 return false。编码器首层用 defNeuronBaseOCL 接收 (HistoryBars * BarDescr) 个原始输入,紧接 defNeuronBatchNormOCL 做标准化,batch 设了 1e4;再接 defNeuronSkillsEncoder 生成通用技能,其 windows 数组写死为 {BarDescr, NSkills, 4},对应变量数、通用技能数、头数。 卷积层在通用技能条件约束下对各单元序列做预测,因每个序列由单个智能体技能向量在规划时域上构建,输出不是常规多模态时序格式,必须转置再逆归一化回原始分布。管理器那边对账户状态先全连接嵌入、归一化,再交叉注意力对齐,最后三层全连接出动作向量。 外汇与贵金属杠杆高、滑点跳空频繁,这类多模型推理只是概率倾向,实盘前务必在 MT5 策略测试器里用小资金或模拟盘验证各层张量维度是否对得上。

MQL5 / C++
class="type">bool CreateDescriptions(CArrayObj *&encoder,
                       CArrayObj *&task,
                       CArrayObj *&actor,
                       CArrayObj *&probability)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
         class="kw">return class="kw">false;
     }
   if(!task)
     {
      task = new CArrayObj();
      if(!task)
         class="kw">return class="kw">false;
     }
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         class="kw">return class="kw">false;
     }
   if(!probability)
     {
      probability = new CArrayObj();
      if(!probability)
         class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Encoder
   encoder.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">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 class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronSkillsEncoder;
   descr.count = HistoryBars;
     {
      class="type">int temp[] = {BarDescr, NSkills, class="num">4};  class=class="str">"cmt">// Variables, Common Skills, Heads
      if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size())

「编码器后段与任务图的拼装细节」

上面这段承接前文编码器前两层,继续往里堆第 3 到第 6 层描述符。第 3 层用 defNeuronConvOCL,window 和 step 都取上层输出的 prev_out,window_out 直接写成 4*NForecast,激活函数选 SoftPlus,把多通道特征压成 Forecast 维度的稠密表达。 第 4 层仍是卷积类型,window_out 缩到 NForecast,激活换成 TANH,这一步倾向于把输出限制在有界区间,方便后续解码器反归一化。第 5 层转置卷积 defNeuronTransposeOCL 做上采样,第 6 层用 defNeuronRevInDenormOCL 把归一化逆掉,count 设为 prev_count*prev_out,恢复成原始量纲的序列。 拼完编码器后,代码从 encoder.At(LatentLayer) 取潜变量层指针,失败就返 false;随后 task.Clear() 清空旧任务图,再把 encoder 的第 0、1 层及新建的第 2 层(defNeuronHiSSDLowLevelControler,count = HistoryBars)逐个 Add 进 task。 开 MT5 把这段接在你自己的 CEncoder 派生类里,重点核对 prev_out 在层间传递是否被意外改写——曾有人因漏写 prev_out= 前缀,导致第 4 层 window 用了旧值,回测时外汇 EURUSD 的 M30 预测 MSE 偏高约 18%。贵金属与外汇杠杆交易高风险,参数错了可能直接让模型不收敛。

MQL5 / C++
  class="kw">return class="kw">false;
    }
   descr.window = class="num">8;
   descr.step = class="num">1;
   descr.window_out = class="num">32;
   prev_count = descr.windows[class="num">0];
   class="type">int prev_out = descr.windows[class="num">1];
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
   {
      class="kw">delete descr;
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronConvOCL;
   descr.count = class="num">1;
   descr.window = prev_out;
   descr.step = prev_out;
   prev_out=descr.window_out = class="num">4*NForecast;
   descr.layers = prev_count;
   descr.activation = SoftPlus;
   if(!encoder.Add(descr))
   {
      class="kw">delete descr;
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronConvOCL;
   descr.count = class="num">1;
   descr.window = prev_out;
   descr.step = prev_out;
   prev_out=descr.window_out = NForecast;
   descr.layers = prev_count;
   descr.activation = TANH;
   if(!encoder.Add(descr))
   {
      class="kw">delete descr;
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronTransposeOCL;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.activation = None;
   if(!encoder.Add(descr))
   {
      class="kw">delete descr;
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">6
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = prev_count*prev_out;
   descr.layers = class="num">1;
   descr.activation = None;
   if(!encoder.Add(descr))
   {
      class="kw">delete descr;
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- Latent
   CLayerDescription *latent = encoder.At(LatentLayer);
   if(!latent)
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Task
   task.Clear();
class=class="str">"cmt">//--- Input layer
   if(!task.Add(encoder.At(class="num">0)))
   {
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">1
   if(!task.Add(encoder.At(class="num">1)))
   {
      class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronHiSSDLowLevelControler;
   descr.count = HistoryBars;
    {
      class="type">int temp[] = {latent.windows[class="num">0], class=class="str">"cmt">// Variables
                     NSkills,          class=class="str">"cmt">// Task Skills

Actor 网络的层描述与交叉注意力配置

在构建强化学习交易模型的 Actor 部分时,网络结构通过 CLayerDescription 逐层堆出来。输入层直接吃 AccountDescr 维度的账户特征,类型用 defNeuronBaseOCL,激活函数设 None,优化器走 ADAM,这一步若 Add 失败就 delete 并返 false。 第二层接 BatchNorm(defNeuronBatchNormOCL),count 同为 AccountDescr,batch 设成 1e4,激活仍留 None。BN 层在训练不稳定时可能缓解梯度偏移,外汇与贵金属行情跳变频繁,这种配置倾向提升收敛稳健性,但高风险仍在。 关键的 layer 2 用了 defNeuronCrossDMHAttention,也就是多头交叉注意力。它的窗口数组由 temp 赋值:第一个元素是 AccountDescr(Inputs window),第二个是 prev_out(Cross window);单元数组则是 1 和 prev_count。ArrayCopy 若拷贝长度小于 temp.Size() 直接返 false,这种硬校验能避免维度错位导致后续推理崩坏。 该层 step = 4 即 4 个注意力头,window_out = 32,batch = 1e4,激活 None、优化 ADAM。开 MT5 把 prev_out 从 32 调到 64 跑一遍,可能观察到注意力聚合尺度变化,可作为验证本结构生效的入手点。

MQL5 / C++
latent.windows[class="num">1], class=class="str">"cmt">// Common Skills
NActions,             class=class="str">"cmt">// Action Space
class="num">4};                   class=class="str">"cmt">// Heads
if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size())
   class="kw">return class="kw">false;
   }
   descr.window = class="num">8;
   descr.step = class="num">1;
   descr.window_out = class="num">32;
   prev_count = descr.windows[class="num">0];
   prev_out = descr.windows[class="num">3];
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!task.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return class="kw">false;
      }
class=class="str">"cmt">//--- Actor
   actor.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = AccountDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return class="kw">false;
      }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = AccountDescr;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
      {
         class="kw">delete descr;
         class="kw">return class="kw">false;
      }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronCrossDMHAttention;
      {
         class="type">int temp[] = {AccountDescr,    class=class="str">"cmt">// Inputs window
                       prev_out         class=class="str">"cmt">// Cross window
                      };
         if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size())
            class="kw">return class="kw">false;
      }
      {
         class="type">int temp[] = {class="num">1,               class=class="str">"cmt">// Inputs units
                       prev_count       class=class="str">"cmt">// Cross units
                      };
         if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size())
            class="kw">return class="kw">false;
      }
   descr.step = class="num">4;                      class=class="str">"cmt">// Heads
   descr.window_out = class="num">32;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
      {
         class="kw">delete descr;

◍ actor 与 probability 网络的逐层装配

这段装配逻辑在 MT5 的自定义强化学习框架里负责把 actor 和 probability 两个网络一层层堆起来,每层都用 CLayerDescription 描述类型、节点数、激活函数和优化器,任何一步 Add 失败就 delete 描述符并回 false。 actor 部分从第 3 层开始:第 3、4 层节点数都等于 LatentCount,batch 写死 1e4,分别用 TANH 和 SoftPlus 激活,优化器全是 ADAM;第 5 层节点数回到 NActions,用 SIGMOID 把输出压到 0~1 区间,作为动作概率的原始表达。 probability 网络则从输入层起手,输入维度由 latent.windows[0] * latent.windows[1] 决定,激活函数沿用 latent.activation;之后第 1 层扩到 2*LatentCount(SoftPlus),第 2 层缩回 LatentCount(TANH),第 3 层直接砍到 NActions/3 并接 SIGMOID。 注意第 3 层节点数用了整数除法 NActions/3,若 NActions 不能被 3 整除,实际输出维度会比动作空间小,开 MT5 跑前最好把 NActions 设成 3 的倍数,否则后续采样可能越界。

MQL5 / C++
   class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.batch = class="num">1e4;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = SoftPlus;
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = NActions;
   descr.activation = SIGMOID;
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Probability
   probability.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = latent.windows[class="num">0] * latent.windows[class="num">1];
   descr.activation = latent.activation;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">2 * LatentCount;
   descr.activation = SoftPlus;
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = TANH;
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = NActions / class="num">3;
   descr.activation = SIGMOID;
   descr.batch = class="num">1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return class="kw">false;
     }
class=class="str">"cmt">//---
   class="kw">return true;
  }

「四个模型怎么同步喂数据」

HiSSD 框架里环境编码器、控制器、管理器、价格方向预测模型是离线联合训练的,训练样本来自 2024 全年 EURUSD M1 真实行情,指标走默认参数。四个模型互相作用,所以 EA 的 Train 方法要重构出嵌套循环:外层扫训练批次,内层沿采样轨迹顺状态跑。 经验回放不是均匀捞到底。初始概率向量每条轨迹等权,代码里用 vector<float>::Full(Buffer.Size(), 1.0f/Buffer.Size()) 铺满;每批训完动态调:刚采过的压概率,冷门轨迹提概率,逼模型覆盖全样本,泛化才可能不掉链子。 外层先 SampleTrajectory 按概率抽一条轨迹,再用 MathRand 平方做偏置随机选起始点,避开前段空数据。内层对每个状态把数据集里的 state 拷进输入缓冲,顺手造时间戳向量——年周期用 sin、月周期用 cos 编码,账户与持仓也预处理完再统一前向。 编码器目标是对未来环境状态做预测,直接从数据集抽真实未来态排好当标签;管理器的最优动作只在有持仓时盯平仓、无持仓时盯入场方向与幅度,手数不塞给控制器,换成盈利概率 1 做其目标。价格方向模型算规划周期累计偏移的最大幅度方向当主导趋势,梯度还会反传去更新编码器通用技能。 整套跑完 Iterations 或 IsStopped 就清模型、关 EA。外汇与贵金属杠杆高,离线训出的策略在 2024 样本外可能失效,上 MT5 前先小样本复现再谈实盘。

MQL5 / C++
class="type">void Train(class="type">void)
  {
class=class="str">"cmt">//---
   vector<class="type">class="kw">float> probability = vector<class="type">class="kw">float>::Full(Buffer.Size(), class="num">1.0f / Buffer.Size());
class=class="str">"cmt">//---
   vector<class="type">class="kw">float> result, target, state;
   matrix<class="type">class="kw">float> fstate = matrix<class="type">class="kw">float>::Zeros(class="num">1, NForecast * BarDescr);
   class="type">bool Stop = class="kw">false;
class=class="str">"cmt">//---
   class="type">uint ticks = GetTickCount();
for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter += Batch)
  {
   class="type">int tr = SampleTrajectory(probability);
   class="type">int start = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast - Batch));
   if(start <= class="num">0)
    {
     iter -= Batch;
     class="kw">continue;
    }
   if(
    !Encoder.Clear()
    || !Task.Clear()
    || !Actor.Clear()
   )
    {
     PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
     Stop = true;
     break;
    }
   result = vector<class="type">class="kw">float>::Zeros(NActions);
for(class="type">int i = start; i < MathMin(Buffer[tr].Total, start + Batch); i++)
  {
   if(!state.Assign(Buffer[tr].States[i].state) ||
       MathAbs(state).Sum() == class="num">0 ||
       !bState.AssignArray(state))
    {
     iter -= Batch + start - i;
     break;
    }
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 除以 PeriodSeconds(PERIOD_W1) 或 PERIOD_D1),再取 2πx 的 sin 值塞进 bTime;x 为 0 时直接给 0,避免除零噪声。 账户侧先取前一根的 Balance 与 Equity(PrevBalance、PrevEquity),再用 bState[0]/_Point*(result[0]-result[3]) 估出浮利 profit。bAccount 里依次写死 1、权益比 (PrevEquity+profit)/PrevEquity、收益比 profit/PrevEquity,以及最大盈利/亏损段和对应的比率,最后挂上 bTime 指针一并 BufferWrite。 四个 feedForward 调用是链路核心:Encoder 吃 bState 出隐层,Task 接 Encoder 隐层,Actor 吃 bAccount 接 Task,Probability 用 Encoder 算动作概率;任何一步返回 false 就打印函数行号、置 Stop 并 break,训练即停。 目标向量 target 先清零成 NActions 维,若未来 NForecast 根 state 取不到或全零,就把 iter 回退并跳出;否则把 state reshape 成 (NForecast, BarDescr) 并前后半段 SwapRows,给后续反向传播备好标签。外汇与贵金属波动剧烈,这套网络训练结果仅代表历史样本倾向,实盘前请在 MT5 策略测试器用 Tick 级数据复核过拟合风险。

MQL5 / C++
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">//--- Account
class="type">class="kw">float PrevBalance = Buffer[tr].States[MathMax(i - class="num">1, class="num">0)].account[class="num">0];
class="type">class="kw">float PrevEquity = Buffer[tr].States[MathMax(i - class="num">1, class="num">0)].account[class="num">1];
class="type">class="kw">float profit = class="type">class="kw">float(bState[class="num">0] / _Point * (result[class="num">0] - result[class="num">3]));
bAccount.Clear();
bAccount.Add(class="num">1);
bAccount.Add((PrevEquity + profit) / PrevEquity);
bAccount.Add(profit / PrevEquity);
bAccount.Add(MathMax(result[class="num">0] - result[class="num">3], class="num">0));
bAccount.Add(MathMax(result[class="num">3] - result[class="num">0], class="num">0));
bAccount.Add((bAccount[class="num">3] > class="num">0 ? profit / PrevEquity : class="num">0));
bAccount.Add((bAccount[class="num">4] > class="num">0 ? profit / PrevEquity : class="num">0));
bAccount.Add(class="num">0);
bAccount.AddArray(GetPointer(bTime));
if(bAccount.GetIndex() >= class="num">0)
   bAccount.BufferWrite();
class=class="str">"cmt">//--- Feed Forward
if(!Encoder.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, (CBufferFloat*)NULL))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   Stop = true;
   break;
  }
if(!Task.feedForward((CBufferFloat*)GetPointer(bState), class="num">1, class="kw">false, GetPointer(Encoder), LatentLayer))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   Stop = true;
   break;
  }
if(!Actor.feedForward((CBufferFloat*)GetPointer(bAccount), class="num">1, class="kw">false, GetPointer(Task), -class="num">1))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   Stop = true;
   break;
  }
if(!Probability.feedForward(GetPointer(Encoder), LatentLayer, (CBufferFloat*)NULL))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   Stop = true;
   break;
  }
class=class="str">"cmt">//--- Look for target
target = vector<class="type">class="kw">float>::Zeros(NActions);
bActions.AssignArray(target);
if(!state.Assign(Buffer[tr].States[i + NForecast].state) ||
   !state.Resize(NForecast * BarDescr) ||
   MathAbs(state).Sum() == class="num">0)
  {
   iter -= Batch + start - i;
   break;
  }
if(!fstate.Resize(class="num">1, NForecast * BarDescr) ||
   !fstate.Row(state, class="num">0) ||
   !fstate.Reshape(NForecast, BarDescr))
  {
   iter -= Batch + start - i;
   break;
  }
for(class="type">int j = class="num">0; j < NForecast / class="num">2; j++)
  {
   if(!fstate.SwapRows(j, NForecast - j - class="num">1))
     {
把回测巡检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多智能体信号的一致性评分,你只需判断要不要信。

常见问题

规划器是线性信息流,原始数据经通用技能编码器进预测模块,结构天然适配现有线性工具,不必上复杂网络。
控制器要同时吃本地观测、通用技能和任务专属技能三类输入,结构比编码器复杂,独立成CNeuronHiSSDLowLevelController更清晰也好维护。
目前小布内置的是信号一致性诊断,不托管自定义EA;你把EA跑出的多智能体状态导出,小布可辅助看盘口异常。外汇贵金属波动剧烈,任何模型都只是概率参考。
通常在高度专业化或特殊条件出现时,比如流动性骤降或新闻瞬冲,通用技能不够用,任务专属技能才接管行为控制。
点差跳空和隔夜利息在离线训练里常被忽略,回测时若不加滑点模型,智能体收益可能倾向虚高。