神经网络变得简单(第 68 部分):离线优先引导政策优化·综合运用
📘

神经网络变得简单(第 68 部分):离线优先引导政策优化·综合运用

第 3/3 篇

轨迹概率重加权与训练采样逻辑

下面这段 MQL5 片段做两件事:先把奖励向量按收益分布做非线性重标定,再在 Train 里用概率向量抽样两条轨迹做配对比较。外汇与贵金属行情高波动,这类重加权只改变样本倾向,不保证任何实盘收益。 奖励重标定里,min 取收益最小值再减 0.1 倍标准差;当最大利润大于该阈值时才进入乘子计算,否则整条 rewards 填 1。Percentile(90) 减 max_profit 得到偏移 k,用绝对值差除以 k(k 为 0 时退化用 -std)再取 exp,形成对极端盈利的放大权重。 Train 函数先拿 GetProbTrajectories 出的概率向量,循环上限由 Iterations 控制。每次抽 tr_p 与 tr_m 两条轨迹,若二者相同则重抽 tr_m,保证配对不同;利润低的换到 tr_m 位。正样本起点 i 用 MathRand 平方分布偏左采样,上限夹在 Total-2*HistoryBars-NBarInPattern 与 20 之间,i 为负就回退本次迭代。 状态回填时,History 数据来自 Buffer[tr_p].States[state].state,账户描述用上一 state 的 balance/equity 做差分比例。注意 PrevBalance 为 0 会导致除零,实盘前应在 MT5 里加保护或确认首态 account[0] 非零。

MQL5 / C++
  class="type">class="kw">double min = rewards.Min() - class="num">0.1 * std;
  if(max_profit > min)
    {
      class="type">class="kw">double k = sorted.Percentile(class="num">90) - max_profit;
      vector<class="type">class="kw">double> multipl = MathAbs(rewards - max_profit) / (k == class="num">0 ? -std : k);
      multipl = exp(multipl);
      rewards = (rewards - min) / (max_profit - min);
      rewards = rewards / (rewards + lanbda) * multipl;
      rewards.ReplaceNan(class="num">0);
    }
  else
      rewards.Fill(class="num">1);
  rewards = rewards / rewards.Sum();
  rewards = rewards.CumSum();
class=class="str">"cmt">//---
  class="kw">return rewards;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Train function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Train(class="type">void)
  {
  vector<class="type">class="kw">double> probability = GetProbTrajectories(Buffer, class="num">0.1f);
  class="type">uint ticks = GetTickCount();
  class="type">bool StopFlag = false;
  for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !StopFlag); iter ++)
    {
      class="type">int tr_p = SampleTrajectory(probability);
      class="type">int tr_m = SampleTrajectory(probability);
      while(tr_p == tr_m)
        tr_m = SampleTrajectory(probability);
      if(Buffer[tr_p].Profit < Buffer[tr_m].Profit)
        {
         class="type">int t = tr_p;
         tr_p = tr_m;
         tr_m = t;
        }
      class=class="str">"cmt">//--- Positive
      class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) *
                     MathMax(Buffer[tr_p].Total - class="num">2 * HistoryBars - NBarInPattern,
                     MathMin(Buffer[tr_p].Total, class="num">20))));
      if(i < class="num">0)
        {
         iter--;
         class="kw">continue;
        }
      Scheduler.Clear();
      for(class="type">int state = i; state < MathMin(Buffer[tr_p].Total - class="num">1 - NBarInPattern,
i + HistoryBars * class="num">2); state++)
        {
         class=class="str">"cmt">//--- History data
         State.AssignArray(Buffer[tr_p].States[state].state);
         class=class="str">"cmt">//--- Account description
         class="type">float PrevBalance = (state == class="num">0 ? Buffer[tr_p].States[state].account[class="num">0] :
Buffer[tr_p].States[state - class="num">1].account[class="num">0]);
         class="type">float PrevEquity = (state == class="num">0 ? Buffer[tr_p].States[state].account[class="num">1] :
Buffer[tr_p].States[state - class="num">1].account[class="num">1]);
         State.Add((Buffer[tr_p].States[state].account[class="num">0] - PrevBalance) / PrevBalance);
         State.Add(Buffer[tr_p].States[state].account[class="num">1] / PrevBalance);

◍ 把账户状态塞进神经网络前的特征拼装

这段逻辑干的事,是把每个交易状态(state)里的账户维度逐个灌进特征向量 State,再丢给调度器做前向推理和反向修正。前 6 个特征分别是权益变动率(相对 PrevEquity)、三个绝对账户量,以及三个相对 PrevBalance 的占比,归一化口径不统一,实盘里要留意分母取错会导致特征尺度爆炸。 时间标签那块用了年/月/周/日四个周期做正弦余弦编码:以 2024.01.01 减 2023.01.01 的秒数当年度基准,再分别除以 MN1、W1、D1 的 PeriodSeconds 取模。x 越小说明该 state 距周期原点越近,三角函数输出越接近 0,模型对“月初效应”可能更敏感。 state>0 时把上一状态的动作向量追加进来,否则填 NActions 个零——这一步让网络能看到“上一步干了啥”。feedForward 若返回失败直接置 StopFlag 并 break,backProp 同理,说明训练循环对外层容错要求极高,任一层崩了整轮就停。 有个隐蔽点是 if(GetTickCount()-ticks>500) 的耗时判定,意味着单轮训练超过 500 毫秒就会触发打印进度,外汇与贵金属行情跳变快,这种阻塞式训练在实盘 EA 里可能拖慢 tick 响应,属于高风险设计。

MQL5 / C++
State.Add((Buffer[tr_p].States[state].account[class="num">1] - PrevEquity) / PrevEquity);
State.Add(Buffer[tr_p].States[state].account[class="num">2]);
State.Add(Buffer[tr_p].States[state].account[class="num">3]);
State.Add(Buffer[tr_p].States[state].account[class="num">4] / PrevBalance);
State.Add(Buffer[tr_p].States[state].account[class="num">5] / PrevBalance);
State.Add(Buffer[tr_p].States[state].account[class="num">6] / PrevBalance);
class=class="str">"cmt">//--- Time label
class="type">class="kw">double x = (class="type">class="kw">double)Buffer[tr_p].States[state].account[class="num">7] / (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;);
State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
x = (class="type">class="kw">double)Buffer[tr_p].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1);
State.Add((class="type">float)MathCos(class="num">2.0 * M_PI * x));
x = (class="type">class="kw">double)Buffer[tr_p].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
x = (class="type">class="kw">double)Buffer[tr_p].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
class=class="str">"cmt">//--- Prev action
if(state > class="num">0)
   State.AddArray(Buffer[tr_p].States[state - class="num">1].action);
else
   State.AddArray(vector<class="type">float>::Zeros(NActions));
class=class="str">"cmt">//--- Feed Forward
if(!Scheduler.feedForward(GetPointer(State), class="num">1, false, (CBufferFloat*)NULL))
   {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
   }
class=class="str">"cmt">//--- Study
Result.AssignArray(Buffer[tr_p].States[state].scheduler);
if(!Scheduler.backProp(Result, (CBufferFloat*)NULL))
   {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   StopFlag = true;
   break;
   }
if(GetTickCount() - ticks > class="num">500)
   {
   class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Scheeduler",

「负样本里的状态特征拼装」

在生成对抗训练用的负样本时,代码先用双重 MathRand() 平方归一化来挑起点 i,分母取 32767 的平方,把随机落点压到靠近 0 的区域,避免均匀撒网。若算出的 i 小于 0 就直接 iter-- 并 continue,相当于这次迭代作废,不计入总轮次。 随后对每个 state 做特征向量拼装:先把历史状态数组 AssignArray 进去,再算账户维度的相对变化——比如 (当前余额-前态余额)/前态余额、权益/余额、权益变化率等 7 个 float 特征,全部除以 PrevBalance 做尺度对齐。这套输入对外汇与贵金属品种同样适用,但这类杠杆交易波动剧烈,特征失真可能导致模型误判,实盘前务必小资金验证。 时间标签部分用账户时间戳分别除以 2023 全年秒数、月线/周线/日线周期秒数,再套 2π 的正弦余弦,把绝对时间折成周期相位。这样网络能隐式吃到月度、周度、日内的节律,而不需要显式日期字段。 把这段直接贴进你的 EA 训练循环,改一下 HistoryBars 与 NBarInPattern,就能在 MT5 里跑出带周期编码的负样本状态缓冲。

MQL5 / C++
i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * MathMax(Buffer[tr_m].Total - class="num">2 * HistoryBars - NBarInPattern, MathMin(Buffer[tr_m].Total, class="num">20)));
if(i < class="num">0)
  {
   iter--;
   class="kw">continue;
  }
Scheduler.Clear();
for(class="type">int state = i; state < MathMin(Buffer[tr_m].Total - class="num">1 - NBarInPattern, i + HistoryBars * class="num">2); state++)
  {
   class=class="str">"cmt">//--- History data
   State.AssignArray(Buffer[tr_m].States[state].state);
   class=class="str">"cmt">//--- Account description
   class="type">float PrevBalance = (state == class="num">0 ? Buffer[tr_m].States[state].account[class="num">0] : Buffer[tr_m].States[state - class="num">1].account[class="num">0]);
   class="type">float PrevEquity = (state == class="num">0 ? Buffer[tr_m].States[state].account[class="num">1] : Buffer[tr_m].States[state - class="num">1].account[class="num">1]);
   State.Add((Buffer[tr_m].States[state].account[class="num">0] - PrevBalance) / PrevBalance);
   State.Add(Buffer[tr_m].States[state].account[class="num">1] / PrevBalance);
   State.Add((Buffer[tr_m].States[state].account[class="num">1] - PrevEquity) / PrevEquity);
   State.Add(Buffer[tr_m].States[state].account[class="num">2]);
   State.Add(Buffer[tr_m].States[state].account[class="num">3]);
   State.Add(Buffer[tr_m].States[state].account[class="num">4] / PrevBalance);
   State.Add(Buffer[tr_m].States[state].account[class="num">5] / PrevBalance);
   State.Add(Buffer[tr_m].States[state].account[class="num">6] / PrevBalance);
   class=class="str">"cmt">//--- Time label
   class="type">class="kw">double x = (class="type">class="kw">double)Buffer[tr_m].States[state].account[class="num">7] / (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;);
   State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
   x = (class="type">class="kw">double)Buffer[tr_m].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1);
   State.Add((class="type">float)MathCos(class="num">2.0 * M_PI * x));
   x = (class="type">class="kw">double)Buffer[tr_m].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
   State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
   x = (class="type">class="kw">double)Buffer[tr_m].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
   State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));

强化学习里的状态回填与误差回传

这段逻辑把上一笔动作和当前状态拼成输入向量,再喂给调度器做前向推理。若 state 大于 0,就取前一个状态的 action 补进 State 数组;否则填一串长度为 NActions 的零向量,避免网络在初始步收到脏数据。 前向一旦失败立刻打印函数名加行号、置 StopFlag 并 break,说明这套训练循环对异常零容忍,实盘里若乱改缓冲区结构可能直接停训。 回传阶段先看前后两步 profit 是否相等:相等就直接把目标设成该状态已有 scheduler 权重;不等则用 forecast 与原有 target 做折半修正(target = forecast - (target - forecast)/2),相当于把误差砍掉一半再反传。 每累计 500 毫秒 tick 就在图表用 Comment 刷一次平均误差,例如跑 1000 代时进度到 50.00% 那行会显示 Error 0.0xxxxxx,方便你肉眼盯收敛。训练结束调 ExpertRemove 自卸,不占后续资源。 别把收敛曲线当圣杯 外汇与贵金属杠杆高、滑点凶,这套 scheduler 误差降到 0.001 级别也只代表回测轨迹拟合好,实盘泛化概率仍要你自己的样本外验证。

MQL5 / C++
  class=class="str">"cmt">//--- Prev action
  if(state > class="num">0)
    State.AddArray(Buffer[tr_m].States[state - class="num">1].action);
  else
    State.AddArray(vector<class="type">float>::Zeros(NActions));
  class=class="str">"cmt">//--- Feed Forward
  if(!Scheduler.feedForward(GetPointer(State), class="num">1, false, (CBufferFloat*)NULL))
    {
    PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
    StopFlag = true;
    break;
    }
  class=class="str">"cmt">//--- Study
  if(Buffer[tr_p].Profit == Buffer[tr_m].Profit)
    Result.AssignArray(Buffer[tr_m].States[state].scheduler);
  else
    {
    vector<class="type">float> target, forecast;
    target.Assign(Buffer[tr_m].States[state].scheduler);
    Scheduler.getResults(forecast);
    target = forecast - (target - forecast) / class="num">2;
    Result.AssignArray(target);
    }
  if(!Scheduler.backProp(Result, (CBufferFloat*)NULL))
    {
    PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
    StopFlag = true;
    break;
    }
  if(GetTickCount() - ticks > class="num">500)
    {
    class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Scheeduler",
(iter + class="num">0.5) * class="num">100.0 / (class="type">class="kw">double)(Iterations), Scheduler.getRecentAverageError());
    Comment(str);
    ticks = GetTickCount();
    }
  }
   }
  Comment("");
class=class="str">"cmt">//---
  PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Scheduler",
Scheduler.getRecentAverageError());
  ExpertRemove();
class=class="str">"cmt">//---
  }
  vector<class="type">class="kw">double> probability = GetProbTrajectories(Buffer, class="num">0.1f);
  class="type">uint ticks = GetTickCount();
  class="type">bool StopFlag = false;
  for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !StopFlag); iter ++)
    {
    class="type">int tr = SampleTrajectory(probability);

◍ 用双重随机与周期编码喂给 Agent 的历史切片

这段逻辑在强化学习回测里负责造一个随机起点 i,再从 Buffer 里抠出一段历史状态喂给 Agent。i 由两次 MathRand() 相乘再除以 32767 的平方归一化,乘以可用长度与下限 20 的较小值,等于把取样窗口压在可控范围,避免越界读脏数据。 如果算出的 i 为负,iter 自减并 continue 跳过本轮,相当于丢弃这次无效抽样。随后 Agent.Clear() 清空上一轮记忆,进入以 state 为游标的 for 循环,上限取 Buffer 总量减模式长度与 i 加两倍 HistoryBars 的较小值。 循环体内先把当前 K 线状态数组赋给 State,再用上一 state 的余额权益算相对变化率——比如 (account[0]-PrevBalance)/PrevBalance 这种归一化差值,能削弱绝对资金量对策略的干扰。 时间特征用了年、月、周、日四个周期的正弦余弦编码:以 2024.01.01 减 2023.01.01 的秒数做年周期分母,再分别用 MN1/W1/D1 的 PeriodSeconds 做月周日周期分母,喂入 sin/cos 让模型隐式记季节律。外汇与贵金属这类高杠杆品种,历史分布漂移快,这种编码也只代表过去样本的概率倾向,实盘仍需警惕过拟合。 最后处理上一动作:state>0 就取前一状态 action 数组,否则补 NActions 个零向量,保证首帧输入维度对齐。开 MT5 把这段贴进 EA 回测框架,改一下 HistoryBars 或 NBarInPattern,能直接看到抽样密度变化。

MQL5 / C++
class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * MathMax(Buffer[tr].Total -
                              class="num">2 * HistoryBars - NBarInPattern, MathMin(Buffer[tr].Total, class="num">20)));
  if(i < class="num">0)
    {
     iter--;
     class="kw">continue;
    }
  Agent.Clear();
  for(class="type">int state = i; state < MathMin(Buffer[tr].Total - class="num">1 - NBarInPattern,
i + HistoryBars * class="num">2); state++)
    {
     class=class="str">"cmt">//--- History data
     State.AssignArray(Buffer[tr].States[state].state);
     class=class="str">"cmt">//--- Account description
     class="type">float PrevBalance = (state == class="num">0 ? Buffer[tr].States[state].account[class="num">0] :
Buffer[tr].States[state - class="num">1].account[class="num">0]);
     class="type">float PrevEquity = (state == class="num">0 ? Buffer[tr].States[state].account[class="num">1] :
Buffer[tr].States[state - class="num">1].account[class="num">1]);
     State.Add((Buffer[tr].States[state].account[class="num">0] - PrevBalance) / PrevBalance);
     State.Add(Buffer[tr].States[state].account[class="num">1] / PrevBalance);
     State.Add((Buffer[tr].States[state].account[class="num">1] - PrevEquity) / PrevEquity);
     State.Add(Buffer[tr].States[state].account[class="num">2]);
     State.Add(Buffer[tr].States[state].account[class="num">3]);
     State.Add(Buffer[tr].States[state].account[class="num">4] / PrevBalance);
     State.Add(Buffer[tr].States[state].account[class="num">5] / PrevBalance);
     State.Add(Buffer[tr].States[state].account[class="num">6] / PrevBalance);
     class=class="str">"cmt">//--- Time label
     class="type">class="kw">double x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] /
(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;);
     State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
     x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_MN1);
     State.Add((class="type">float)MathCos(class="num">2.0 * M_PI * x));
     x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_W1);
     State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
     x = (class="type">class="kw">double)Buffer[tr].States[state].account[class="num">7] / (class="type">class="kw">double)PeriodSeconds(PERIOD_D1);
     State.Add((class="type">float)MathSin(class="num">2.0 * M_PI * x));
     class=class="str">"cmt">//--- Prev action
     if(state > class="num">0)
       State.AddArray(Buffer[tr].States[state - class="num">1].action);
     else
       State.AddArray(vector<class="type">float>::Zeros(NActions));

「训练循环里的调度与误差回显」

这段代码把强化学习 agent 的单轮训练拆成了三段:先灌调度状态,再前向推理,最后做策略回传。任何一步返回失败就置 StopFlag 并 break,避免脏数据继续污染权重。 前向调用 feedForward 的第三个参数写死为 false,意味着本次不触发训练模式下的随机探索,仅做确定性推断;backProp 的第二个目标缓冲传了 NULL,说明标签直接取自 Buffer[tr].States[state].action 数组,属于同态策略回放。 每累计耗时超过 500 毫秒(GetTickCount 差值判定),就把当前迭代进度与 Agent.getRecentAverageError() 通过 Comment 打到图表左上角。这个 500ms 的节流阈值很关键——设太小会拖慢回测,设太大则界面反馈滞后,建议在 MT5 策略测试器里按自己 CPU 单核性能微调。 循环结束后清空 Comment 并打印最终平均误差,随后调用 ExpertRemove() 自卸载。外汇与贵金属市场高风险,这类离线训练 EA 仅适合在历史数据上验证网络收敛性,切勿直接挂实盘。

MQL5 / C++
class=class="str">"cmt">//--- Scheduler
   State.AddArray(Buffer[tr].States[state].scheduler);
   class=class="str">"cmt">//--- Feed Forward
   if(!Agent.feedForward(GetPointer(State), class="num">1, false, (CBufferFloat*)NULL))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      StopFlag = true;
      break;
     }
   class=class="str">"cmt">//--- Policy study
   Result.AssignArray(Buffer[tr].States[state].action);
   if(!Agent.backProp(Result, (CBufferFloat*)NULL))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      StopFlag = true;
      break;
     }
   class=class="str">"cmt">//---
   if(GetTickCount() - ticks > class="num">500)
     {
      class="type">class="kw">string str = StringFormat("%-15s %class="num">5.2f%% -> Error %class="num">15.8f\n", "Agent",
iter * class="num">100.0 / (class="type">class="kw">double)(Iterations), Agent.getRecentAverageError());
      Comment(str);
      ticks = GetTickCount();
     }
   }
  }
 Comment("");
class=class="str">"cmt">//---
 PrintFormat("%s -> %d -> %-15s %class="num">10.7f", __FUNCTION__, __LINE__, "Agent",
Agent.getRecentAverageError());
 ExpertRemove();
class=class="str">"cmt">//---
 }

EURUSD H1 上跑通的一次离线策略实测

这套离线优先引导策略优化(OPPO)的实现,训练与测试都钉在 EURUSD 的 H1 周期:用 2023 年前 7 个月历史数据训练,拿 2023 年 8 月数据做样本外测试。因为轨迹保存结构改了,旧工作的样本全废,重新在笔记本上从随机权重跑 500 条轨迹,连续烧了 3 天。 训练拆给 2 个独立智能系统并行跑,并且照例做了训练集迭代筛选。个人观察到一个硬约束:想让 Agent 学到能盈利的行为,训练集里必须有正验算——靠环境探索补采轨迹,或像前文那样直接灌 EA 轨迹、复制信号都行;有正验算后,收敛明显更快。 测出来模型在训练集和测试集都盈利。测试段 EA 共下 180 笔单,盈利平仓占比近 49%,盈亏单数基本对等;但平均盈利比平均亏损高 30%,盈利因子 1.25,余额线虽有急拉急跌,上行趋势还在。外汇/贵金属这类品种杠杆高、回撤凶,1.25 的因子只说明该样本段概率占优,换周期或品种可能直接变脸,开 MT5 用同样数据复一遍最实在。

◍ 把轨迹当整体来评估政策

OPPO 思路里最反直觉的一点,是把整条轨迹看成由单一政策生成的整体,而不是逐笔去算每个动作的贡献。在外汇与贵金属这种奖励稀疏、反馈延迟的环境里,单独给某次开仓贴「好 / 坏」标签往往失真,模型反而容易被噪声带偏。 我们在 MT5 用 MQL5 落了这套方法,和原论文有偏差,但训出的政策在训练段和样本外测试段都出了盈利。这至少说明:不依赖显式奖励函数,也有可能构出能跑的策略原型。 要提醒的是,外汇 / 贵金属杠杆高、回撤快,这类演示程序只验证技术可行性,直接上实盘风险很大。真要用的话,先在策略测试器里把历史段和 walk-forward 段都跑一遍再谈。

「把这条线请下神坛」

整套 LSTM 优化实验落到工程层,就是七个文件在 MT5 里各司其职:Research.mq5 与 StudyAgent.mq5 两只 EA 分别跑样本收集和智能体训练,StudyScheduler.mq5 调度偏好模型,Test.mq5 做闭环验证;Trajectory.mqh 定义系统状态结构,NeuroNet.mqh 与 NeuroNet.cl 则把网络构建和 OpenCL 并行计算下沉到类库与代码库。 随文提供的 MQL5.zip 约 672.66 KB,在 MT5 里解压后直接编译即可复现文中所说的多元时间序列预测流程;但外汇与贵金属杠杆高、滑点跳空频繁,神经网络的样本外表现可能随品种与周期漂移,实盘前务必用 Test.mq5 在至少三年历史数据上跑通回测。 所谓「AI 预测圣杯」从来只是工程链路上的一串参数,把它请下神坛,你才愿意动手改一处学习率、换一组特征,去 MT5 里看曲线自己说话。

常见问题

按采样策略和 behaving 策略的概率比做重要性权重裁剪,再把权重归一化后喂给损失函数,避免极端样本主导梯度。
余额除以初始资金、浮亏转正数占比、持仓手数取对数,统一缩到 [-1,1],再和价格特征拼接进输入向量。
小布可自动解析你的特征拼接代码并标出维度不匹配或归一化遗漏的字段,直接在品种页给出诊断提示。
必须同构,否则 Agent 会把特征来源当隐变量;缺失字段用零向量占位并加 mask 位区分。
低频周期用固定正弦编码省参数,高频或非线性周期用可学习嵌入,每 20 轮回显一次误差看是否过拟合。