神经网络变得轻松(第四十七部分):连续动作空间·进阶篇
🧠

神经网络变得轻松(第四十七部分):连续动作空间·进阶篇

(2/3)· 仅靠买卖持有三选项的训练只是玩具,资金管理缺位如何撑起实盘策略

新手友好 第 2/3 篇
把强化学习代理者锁死在买/卖/等待/平仓四个选项里,等于默认每笔都最小手数裸奔。没有止损止盈的模型在柱线内反向波动时毫无缓冲,这种训练结果离可盈利策略还差一层资本与风险管理的骨架。

◍ 神经网络前向传播与梯度回传的指针守卫

这段 CNet 成员函数展示了 OpenCL 加速神经网络在 MT5 里的典型容错写法:前向 feedForward 从 layers.At(0) 取首神经元,逐层调用 neuron.FeedForward 把上一层输出喂给下一层,任意一层指针无效或更新失败立即 delete second 并返回 false,成功才返回 true。 反向 backPropGradient 先校验 layers 与 opencl 非空,再从最后一层向前循环到 layerNum=0 计算隐藏层梯度(calcHiddenGradients),随后由后向前调用 UpdateInputWeights 更新权重;循环里只要 CheckPointer 返回 POINTER_INVALID 或神经元方法返回 false 就直接退出,避免悬空指针污染显存缓冲。 在 MT5 策略测试器里接这段逻辑时,留意 del_second 标志:它为 true 时会释放传入的第二个输入缓冲,若你在外部复用同一 CBufferFloat 对象,误设该标志可能导致后续 bar 计算读到已释放内存。外汇与贵金属模型训练本身属高风险操作,过拟合后实盘胜率可能显著低于回测。

MQL5 / C++
  {
      if(del_second)
         class="kw">delete second;
      class="kw">return false;
   }
   CNeuronBaseOCL *neuron = layer.At(class="num">0);
   layer = layers.At(class="num">0);
   if(!layer)
   {
      if(del_second)
         class="kw">delete second;
      class="kw">return false;
   }
   if(layer.At(class="num">0) != neuron)
      if(!layer.Update(class="num">0, neuron))
         {
          if(del_second)
             class="kw">delete second;
          class="kw">return false;
         }
   for(class="type">int l = class="num">1; l < layers.Total(); l++)
      {
      layer = layers.At(l);
      neuron = layer.At(class="num">0);
      layer = layers.At(l - class="num">1);
      if(!neuron.FeedForward(layer.At(class="num">0), second))
         {
          if(del_second)
             class="kw">delete second;
          class="kw">return false;
         }
      }
class=class="str">"cmt">//---
   if(del_second)
      class="kw">delete second;
   class="kw">return true;
   }
class="type">bool CNet::backPropGradient(CBufferFloat *SecondInput = NULL, CBufferFloat *SecondGradient = NULL)
   {
   if(
! layers ||
! opencl)
      class="kw">return false;
   CLayer *currentLayer = layers.At(layers.Total() - class="num">1);
   CNeuronBaseOCL *neuron = NULL;
   if(CheckPointer(currentLayer) == POINTER_INVALID)
      class="kw">return false;
class=class="str">"cmt">//--- Calc Hidden Gradients
   class="type">int total = layers.Total();
   for(class="type">int layerNum = total - class="num">2; layerNum >= class="num">0; layerNum--)
      {
      CLayer *nextLayer = currentLayer;
      currentLayer = layers.At(layerNum);
      if(CheckPointer(currentLayer) == POINTER_INVALID)
         class="kw">return false;
      neuron = currentLayer.At(class="num">0);
      if(!neuron || !neuron.calcHiddenGradients(nextLayer.At(class="num">0), SecondInput, SecondGradient))
         class="kw">return false;
      }
   CLayer *prevLayer = layers.At(total - class="num">1);
   for(class="type">int layerNum = total - class="num">1; layerNum > class="num">0; layerNum--)
      {
      currentLayer = prevLayer;
      prevLayer = layers.At(layerNum - class="num">1);
      neuron = currentLayer.At(class="num">0);
      if(!neuron.UpdateInputWeights(prevLayer.At(class="num">0), SecondInput))
         class="kw">return false;
      }
   class="type">bool result=false;
   for(class="type">int layerNum = class="num">0; layerNum < total; layerNum++)
      {
      currentLayer = layers.At(layerNum);
      CNeuronBaseOCL *temp = currentLayer.At(class="num">0);

「Actor 网络的四层卷积堆叠」

在强化学习交易代理里,Actor 负责输出动作策略,其结构描述由 CreateDescriptions 函数填充。若传入的 actor 指针为空,代码会 new 一个 CArrayObj 并做空指针回退,保证后续 Add 操作不崩。 输入层用 defNeuronBaseOCL,节点数 = HistoryBars * BarDescr,激活设为 None,优化用 ADAM;紧跟的 BatchNorm 层 batch 写死 1000,对输入做归一化,能缓解外汇小时线均值漂移带来的梯度问题。 随后两层 defNeuronConvOCL 是重点:第一层 window=2、window_out=8、步长 1,把 prev_count 减 1;第二层 window=8、步长 8、window_out=8,相当于对特征做 8 倍降采样。最后一层全连接 256 节点、LReLU 激活。 每层 Add 失败都会 delete descr 并返回 false,MT5 里跑这套若报 false,优先查前层 count 是否算错——比如 prev_count 在卷积后没更新就会让维度对不上。贵金属与外汇杠杆高,模型结构错配可能放大回测虚高,实盘前务必小样本验证。

MQL5 / C++
class="type">bool CreateDescriptions(CArrayObj *actor, CArrayObj *critic)
  {
class=class="str">"cmt">//---
  CLayerDescription *descr;
class=class="str">"cmt">//---
  if(!actor)
    {
     actor = new CArrayObj();
     if(!actor)
        class="kw">return false;
    }
  if(!critic)
    {
     critic = new CArrayObj();
     if(!critic)
        class="kw">return false;
    }
class=class="str">"cmt">//--- Actor
  actor.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.window = class="num">0;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!actor.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(!actor.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 = defNeuronConvOCL;
  prev_count = descr.count = prev_count - class="num">1;
  descr.window = class="num">2;
  descr.step = class="num">1;
  descr.window_out = class="num">8;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!actor.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 = defNeuronConvOCL;
  prev_count = descr.count = prev_count;
  descr.window = class="num">8;
  descr.step = class="num">8;
  descr.window_out = class="num">8;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!actor.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 = class="num">256;
  descr.optimization = ADAM;
  descr.activation = LReLU;
  if(!actor.Add(descr))
    {
     class="kw">delete descr;

Actor 与 Critic 的深层网络拼装

这段逻辑在 MT5 里把强化学习的双网络逐层堆起来:Actor 从第 5 层到第 9 层,分别用 128、256、256、256、6 个神经元收口,最后一层 6 个输出单元大概率对应六类动作空间。 Critic 侧先清结构,再建输入层 256 节点、随后第 1 层用 Concatenate 把前层 256 维窗口与步长 6 的动作拼接成 128 维,第 2 层回到 128 个 LReLU 基元。 每层都先 new 描述符、失败即 delete 并返回 false,这种写法在 EA 初始化阶段若显存或对象池紧张会直接中断,开 MT5 跑前先确认终端版本对 OCL 类支持。 外汇与贵金属行情下用此类网络做决策属高风险,过拟合与滑点可能让回测倾向失真,参数只作验证用。

MQL5 / C++
   class="kw">return false;
    }
class=class="str">"cmt">//--- layer class="num">5
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">128;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.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 = defNeuronConcatenate;
   descr.count = class="num">256;
   descr.window = prev_count;
   descr.step = AccountDescr;
   descr.optimization = ADAM;
   descr.activation = LReLU;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">7
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">256;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">8
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">256;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">9
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   descr.count = class="num">6;
   descr.optimization = ADAM;
   descr.activation = LReLU;
   if(!actor.Add(descr))
     {
      class="kw">delete descr;
      class="kw">return false;
     }
class=class="str">"cmt">//--- Critic
   critic.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = class="num">256;
   descr.window = class="num">0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!critic.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 = defNeuronConcatenate;
   descr.count = class="num">128;
   descr.window = prev_count;
   descr.step = class="num">6;
   descr.optimization = ADAM;
   descr.activation = LReLU;
   if(!critic.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 = defNeuronBaseOCL;
   descr.count = class="num">128;
   descr.activation = LReLU;

◍ 判别器后几层与初始化里的模型装载

判别网络在第三、四层继续用 ADAM 优化器堆叠。第三层为 128 个 LReLU 激活的基元神经元,第四层退化为 1 个无激活神经元,输出标量评分;任何一层 Add 失败就 delete 描述符并回 false,避免悬空对象。 宏 LatentLayer 被定义为 6,暗示潜变量维度,后续采样或噪声注入会用到这个常量,改它等于改网络瓶颈宽度。 OnInit 里先把 RSI(周期 RSIPeriod)、CCI(CCIPeriod)、ATR(ATRPeriod)、MACD(快/慢/信号周期) 全部按当前图表品种与时间框架创建,并统一 BufferResize 到 HistoryBars。若四个指标任一创建或扩容失败,直接 INIT_FAILED,EA 不会上线交易。 模型装载段尝试从 FileName 指向的路径读 Act.nnw 与 Crt.nnw,同时把同一份权重塞进 TargetActor / TargetCritic,做双网络影子拷贝;若文件缺失则现场 new 出 actor 与 critic 数组并调 CreateDescriptions 重建拓扑。外汇与贵金属杠杆高,这类未训练模型直接跑实盘可能瞬间回撤,建议先开 MT5 用策略测试器跑空载看 OnInit 日志。

MQL5 / C++
  descr.optimization = ADAM;
  if(!critic.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 = defNeuronBaseOCL;
  descr.count = class="num">128;
  descr.activation = LReLU;
  descr.optimization = ADAM;
  if(!critic.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 = class="num">1;
  descr.optimization = ADAM;
  descr.activation = None;
  if(!critic.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="macro">#define                     LatentLayer   class="num">6
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  if(!Symb.Name(_Symbol))
      class="kw">return INIT_FAILED;
  Symb.Refresh();
class=class="str">"cmt">//---
  if(!RSI.Create(Symb.Name(), TimeFrame, RSIPeriod, RSIPrice))
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  if(!CCI.Create(Symb.Name(), TimeFrame, CCIPeriod, CCIPrice))
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  if(!ATR.Create(Symb.Name(), TimeFrame, ATRPeriod))
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
  if(!MACD.Create(Symb.Name(), TimeFrame, FastPeriod, SlowPeriod, SignalPeriod, MACDPrice))
      class="kw">return INIT_FAILED;
  if(!RSI.BufferResize(HistoryBars) || !CCI.BufferResize(HistoryBars) ||
     !ATR.BufferResize(HistoryBars) || !MACD.BufferResize(HistoryBars))
    {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//---
  if(!Trade.SetTypeFillingBySymbol(Symb.Name()))
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- load models
  class="type">float temp;
  if(!Actor.Load(FileName + "Act.nnw", temp, temp, temp, dtStudied, true) ||
     !Critic.Load(FileName + "Crt.nnw", temp, temp, temp, dtStudied, true) ||
     !TargetActor.Load(FileName + "Act.nnw", temp, temp, temp, dtStudied, true) ||
     !TargetCritic.Load(FileName + "Crt.nnw", temp, temp, temp, dtStudied, true))
    {
      CArrayObj *actor = new CArrayObj();
      CArrayObj *critic = new CArrayObj();
      if(!CreateDescriptions(actor, critic))

「初始化与退出时的网络闭环校验」

EA 初始化阶段对 actor / critic 网络做硬性维度校验:动作空间必须严格等于 6,否则直接返回 INIT_FAILED。这段代码里 Result.Total() != 6 的判定,就是防止你改了输出层却忘了同步动作枚举。 输入状态维度也有约束:Actor 第 0 层输出必须等于 HistoryBars * BarDescr,Critic 第 0 层输入必须等于 Actor 隐层(LatentLayer)输出维度。任何一处对不上,Init 就终止,不会带着错配网络跑实盘。 OnDeinit 里会把 TargetActor / TargetCritic 用 Tau 系数软更新回主网络,并落盘为 Act.nnw / Crt.nnw。注意这里存的是 target 网络权重,不是在线训练的 actor——回测中断后重载,倾向从 target 恢复策略稳定性。 外汇与贵金属杠杆高、滑点跳空频繁,这类 RL 模型在 MT5 实盘前务必用历史数据多轮验证维度与权重文件一致性,可能避免初始化静默失败。

MQL5 / C++
  class="kw">delete actor;
  class="kw">delete critic;
  class="kw">return INIT_FAILED;
  }
  if(!Actor.Create(actor) || !Critic.Create(critic) ||
     !TargetActor.Create(actor) || !TargetCritic.Create(critic))
    {
     class="kw">delete actor;
     class="kw">delete critic;
     class="kw">return INIT_FAILED;
    }
  class="kw">delete actor;
  class="kw">delete critic;
  class=class="str">"cmt">//---
   }
 COpenCLMy *opencl = Actor.GetOpenCL();
 Critic.SetOpenCL(opencl);
 TargetActor.SetOpenCL(opencl);
 TargetCritic.SetOpenCL(opencl);
 Actor.getResults(Result);
 if(Result.Total() != class="num">6)
   {
    PrintFormat("The scope of the actor does not match the actions count(%d <> %d)", class="num">6, Result.Total());
    class="kw">return INIT_FAILED;
   }
 ActorResult = vector<class="type">float>::Zeros(class="num">6);
class=class="str">"cmt">//---
  Actor.GetLayerOutput(class="num">0, Result);
  if(Result.Total() != (HistoryBars * BarDescr))
    {
     PrintFormat("Input size of Actor doesn&class="macro">#x27;t match state description(%d <> %d)", Result.Total(), (HistoryBars * BarDescr));
     class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//---
  Actor.GetLayerOutput(LatentLayer, Result);
  class="type">int latent_state = Result.Total();
  Critic.GetLayerOutput(class="num">0, Result);
  if(Result.Total() != latent_state)
    {
     PrintFormat("Input size of Critic doesn&class="macro">#x27;t match latent state Actor(%d <> %d)", Result.Total(), latent_state);
     class="kw">return INIT_FAILED;
    }
  PrevBalance = AccountInfoDouble(ACCOUNT_BALANCE);
  PrevEquity = AccountInfoDouble(ACCOUNT_EQUITY);
  FirstBar = true;
  Gradient.BufferInit(AccountDescr, class="num">0);
  Gradient.BufferCreate(opencl);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  TargetActor.WeightsUpdate(GetPointer(Actor), Tau);
  TargetCritic.WeightsUpdate(GetPointer(Critic), Tau);
  TargetActor.Save(FileName + "Act.nnw", Actor.getRecentAverageError(), class="num">0, class="num">0, TimeCurrent(), true);
  TargetCritic.Save(FileName + "Crt.nnw", Critic.getRecentAverageError(), class="num">0, class="num">0, TimeCurrent(), true);
  class="kw">delete Result;
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//---

把网络输出变成下单手数与止损位

新 K 线触发后先回拷历史 Rate 数组并翻转序列,再逐个 Refresh 指标与品种对象,这一步卡住就直接 return,避免用脏数据喂网络。 如果是非首根 K 线,先用 Target 网络做前向、用余额差加 Critic 估值算 reward;当账户余额没动且多空信号都为 0 时,reward 再扣 1,用来惩罚“看了半天不下单”的惰性策略。外汇与贵金属杠杆高,这种惩罚项只是概率上引导模型更积极,不保证任何收益。 主网络前向跑完,Critic 结果会人为加一个绝对值的 0.0001 倍噪声再反传,相当于给梯度加点扰动做探索;随后把 Account 快照存进 PrevAccount 并建 OpenCL 缓冲,供下一根 K 线算梯度用。 真正下单前,把 Actor 输出的 vector 转成 lot:buy_lot 用 ActorResult[0] 除以最小手数四舍五入,sell_lot 取 [3];买单价差保护 stops 取 StopsLevel 与 1 的较大值乘 Point。买 TP 是 Ask 加 [1]、买 SL 是 Ask 减 [2],均按品种 Digits 归一化。打开 MT5 把这段接在 EA 的 OnTick 末尾,就能看到神经网络决策如何落地成具体挂单价。

MQL5 / C++
  if(!IsNewBar())
        class="kw">return;
class=class="str">"cmt">//---
   class="type">int bars = CopyRates(Symb.Name(), TimeFrame, iTime(Symb.Name(), TimeFrame, class="num">1), HistoryBars, Rates);
   if(!ArraySetAsSeries(Rates, true))
        class="kw">return;
class=class="str">"cmt">//---
   RSI.Refresh();
   CCI.Refresh();
   ATR.Refresh();
   MACD.Refresh();
   Symb.Refresh();
   Symb.RefreshRates();
   if(!FirstBar)
     {
       if(!TargetActor.feedForward(GetPointer(State), class="num">1, false, GetPointer(Account)))
         class="kw">return;
       if(!TargetCritic.feedForward(GetPointer(TargetActor), LatentLayer, GetPointer(TargetActor)))
         class="kw">return;
       TargetCritic.getResults(Result);
       class="type">float reward = (class="type">float)(account[class="num">0] - PrevBalance + Result[class="num">0]);
       if(account[class="num">0] == PrevBalance)
         if((buy_value + sell_value) == class="num">0)
           reward -= class="num">1;
       Result.Update(class="num">0, reward);
       if(!Critic.backProp(Result, GetPointer(Actor)) || !Actor.backPropGradient(GetPointer(PrevAccount), GetPointer(Gradient)))
         class="kw">return;
     }
   if(!Actor.feedForward(GetPointer(State), class="num">1, false, GetPointer(Account)))
     class="kw">return;
   if(!Critic.feedForward(GetPointer(Actor), LatentLayer, GetPointer(Actor)))
     class="kw">return;
   if(!FirstBar)
     {
       Critic.getResults(Result);
       Result.Update(class="num">0, Result.At(class="num">0) + MathAbs(Result.At(class="num">0) * class="num">0.0001f));
       Critic.TrainMode(false);
       if(!Critic.backProp(Result, GetPointer(Actor)) || !Actor.backPropGradient(GetPointer(Account), GetPointer(Gradient)))
         class="kw">return;
       Critic.TrainMode(true);
     }
   FirstBar = false;
   PrevAccount.AssignArray(GetPointer(Account));
   PrevAccount.BufferCreate(Actor.GetOpenCL());
   PrevBalance = account[class="num">0];
   PrevEquity = account[class="num">1];
   vector<class="type">float> temp;
   Actor.getResults(temp);
   class="type">float delta = MathAbs(ActorResult - temp).Sum();
   ActorResult = temp;
class=class="str">"cmt">//---
   class="type">class="kw">double min_lot = Symb.LotsMin();
   class="type">class="kw">double stops = MathMax(Symb.StopsLevel(), class="num">1) * Symb.Point();
   class="type">class="kw">double buy_lot = MathRound((class="type">class="kw">double)ActorResult[class="num">0] / min_lot) * min_lot;
   class="type">class="kw">double sell_lot = MathRound((class="type">class="kw">double)ActorResult[class="num">3] / min_lot) * min_lot;
   class="type">class="kw">double buy_tp = NormalizeDouble(Symb.Ask() + ActorResult[class="num">1], Symb.Digits());
   class="type">class="kw">double buy_sl = NormalizeDouble(Symb.Ask() - ActorResult[class="num">2], Symb.Digits());
把动作空间诊断交给小布
这些关于离散动作组合爆炸和连续空间必要性的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到代理者决策维度的提示,你只需判断要不要接这套逻辑。

常见问题

缺资金与风险管理模块,默认最小手数且无止损,柱线内不利波动会直接侵蚀余额,仅适合验证训练方式而非实盘。
选项数量与灵活性需妥协,组合可能过百,输出端神经元激增、训练样本与耗时同步上涨,模型复杂度陡增。
由扮演者基于状态输出连续动作,评论者评估动作价值,二者协同令代理者从连续范围选手数与挂单位。
可以,小布内置了动作维度诊断,打开品种页能直接看到该代理者是否覆盖手数与风控参数,省去手动核算。
代理者从连续数值挑参数,不必预切档位,对交易量、止损止盈级别的管理更细,也回避了组合爆炸。