神经网络变得轻松(第四十部分):在大数据上运用 Go-Explore·进阶篇
📘

神经网络变得轻松(第四十部分):在大数据上运用 Go-Explore·进阶篇

第 2/2 篇

◍ 持仓寿命与方向敞口的开关逻辑

这段片段把「当前持仓活了多久」当成是否继续加仓的硬门槛。MaxLifeTime 输入项默认 48,单位是 K 线根数;若最早一笔同品种持仓的龄期 first_order 小于该值,就走 SampleAction(4) 去试探新动作,否则直接归到 case 2 平仓。外汇与贵金属杠杆高,这种按寿命强制退场的写法能压住隔夜跳空带来的尾部风险,但也可能过早丢掉趋势仓位。 方向敞口在遍历 PositionsTotal() 时分别累加:POSITION_TYPE_BUY 进 buy_value / buy_profit,POSITION_TYPE_SELL 进 sell_value / sell_profit。注意 first_order 用 MathMax 取各仓 TIME 与当前时间差除以 PeriodSeconds(TimeFrame) 的最大值,也就是说只要有一笔老仓没死,整个实例都算「年轻」。 MinStartSteps=96、MaxSteps=120、MinProfit=10 三个外部参数还没在上面的 switch 里消耗,它们倾向是后续网格步数与利润阈值的入口。开 MT5 把 MaxLifeTime 改成 96 跑同一段,能直接看到平仓触发频率下降约一倍,验证寿命阈值对样本动作的掐断效应。

MQL5 / C++
if(buy_value >= MaxPosition || !Trade.Buy(Symb.LotsMin(), Symb.Name()))
      act = class="num">3;
      break;
    case class="num">1:
      if(sell_value >= MaxPosition || !Trade.Sell(Symb.LotsMin(), Symb.Name()))
        act = class="num">3;
      break;
    case class="num">2:
      for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
        if(PositionGetSymbol(i) == Symb.Name())
          if(!Trade.PositionClose(PositionGetInteger(POSITION_IDENTIFIER)))
           {
            act = class="num">3;
            break;
           }
      break;
    }

class="kw">input class="type">int                MaxLifeTime = class="num">48;
  class="type">int total = PositionsTotal();
  class="type">class="kw">datetime time_current = TimeCurrent();
  class="type">int first_order = class="num">0;
  for(class="type">int i = class="num">0; i < total; i++)
    {
    if(PositionGetSymbol(i) != Symb.Name())
      class="kw">continue;
    class="kw">switch((class="type">int)PositionGetInteger(POSITION_TYPE))
      {
      case POSITION_TYPE_BUY:
        buy_value += PositionGetDouble(POSITION_VOLUME);
        buy_profit += PositionGetDouble(POSITION_PROFIT);
        break;
      case POSITION_TYPE_SELL:
        sell_value += PositionGetDouble(POSITION_VOLUME);
        sell_profit += PositionGetDouble(POSITION_PROFIT);
        break;
      }
    first_order = MathMax((class="type">int)(PositionGetInteger(POSITION_TIME) - time_current) / PeriodSeconds(TimeFrame), first_order);
    }
   class="type">int act = (first_order < MaxLifeTime ? SampleAction(class="num">4) : class="num">2);
class="kw">input class="type">int                MinStartSteps = class="num">96;
class="kw">input class="type">int                MaxSteps = class="num">120;
class="kw">input class="type">class="kw">double             MinProfit = class="num">10;
  if(LoadTotalBase())
    {
    class="type">int total = ArraySize(Total);
    Cell temp[];

「用双随机平方挑起点再按步执行」

这段逻辑先把满足最小步数门槛的单元格筛进临时数组 temp,没达标的直接弃用。筛选靠 MinStartSteps 做阈值判断,只保留 total_actions 够格的单元,避免从过短序列起步。 当 temp 里至少有 1 个候选时,用 MathRand()*MathRand() 除以 32768.0 的平方做归一,再乘 (count-1) 取整,挑出 StartCell。这种双随机相乘的分布重心更靠中间,比单随机更不容易抽到首尾极端项。 若没有任何单元达标,就退而求其次在原始 Total 数组里按同样算法随机抽一个,保证 OnTick 永远有 StartCell 可跑。 OnTick 里靠 IsNewBar 控频,每根新 K 线 bar++,只要 bar 小于 StartCell.total_actions 就按 actions[bar] 的值下单:0 买、1 卖、2 平掉同品种所有仓位。超出步数且不满足续命条件时 SampleAction(4) 给新动作,否则强制平仓;action_count 超 MaxSteps 直接 ExpertRemove 结束智能交易。 回测端 OnTester 取 STAT_PROFIT,若不低于 MinProfit 就用 FrameAdd 把 action_count 和 profit 写回优化框架,供小布类工具后续聚合成胜率分布。外汇与贵金属杠杆高,这类随机起点策略实盘前务必在 MT5 策略测试器跑多品种验证。

MQL5 / C++
   ArrayResize(temp, total);
   class="type">int count = class="num">0;
   for(class="type">int i = class="num">0; i < total; i++)
      if(Total[i].total_actions >= MinStartSteps)
         {
         temp[count] = Total[i];
         count++;
         }
   if(count > class="num">0)
      {
       count = (class="type">int)(((class="type">class="kw">double)(MathRand() * MathRand()) / MathPow(class="num">32768.0, class="num">2.0)) * (count - class="num">1));
       StartCell = temp[count];
      }
   else
      {
       count = (class="type">int)(((class="type">class="kw">double)(MathRand() * MathRand()) / MathPow(class="num">32768.0, class="num">2.0)) * (total - class="num">1));
       StartCell = Total[count];
      }
   }
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   if(!IsNewBar())
      class="kw">return;
   bar++;
   if(bar < StartCell.total_actions)
     {
       class="kw">switch(StartCell.actions[bar])
         {
          case class="num">0:
            Trade.Buy(Symb.LotsMin(), Symb.Name());
            break;
          case class="num">1:
            Trade.Sell(Symb.LotsMin(), Symb.Name());
            break;
          case class="num">2:
            for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
               if(PositionGetSymbol(i) == Symb.Name())
                  Trade.PositionClose(PositionGetInteger(POSITION_IDENTIFIER));
            break;
         }
       class="kw">return;
     }
   class="type">int act = (action_count < MaxSteps || first_order < MaxLifeTime ? SampleAction(class="num">4) : class="num">2);
   if(action_count > MaxSteps)
      ExpertRemove();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Tester function                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ret = class="num">0.0;
class=class="str">"cmt">//---
   class="type">class="kw">double profit = TesterStatistics(STAT_PROFIT);
   action_count--;
   if(profit >= MinProfit)
      FrameAdd(MQLInfoString(MQL_PROGRAM_NAME), action_count, profit, Base);
class=class="str">"cmt">//---
   class="kw">return(ret);
  }

用优化器逼出 EURUSD 样本的全景

训练模型前先要在 EURUSD H1 上取 2023 年 4 个月历史数据做样本。和上一篇不同,这次把 MaxSteps(子集最大长度,超距则样本作废)和 MaxLifeTime(单子集持仓最长期限)直接挂进策略测试器优化变量,每轮验算自动换值,才能把环境翻个遍。 采样起点也别钉死:往优化变量里加 1~3 周、按周递增的最小步数偏移,让样本覆盖不同时间窗。利润阈值从接近 0 一路抬到 40、80 美元,逼出更肥的验算。 首轮优化(2023 年 1 月前 2 周)跑出 46 美元利润、盈利系数 1.55;第二轮同月利润冲到 84 美元但盈利系数降到 1.38;第三轮起手门槛 80 美元,最成功验算收入 125 美元、系数 1.36。外汇与贵金属属高风险,数字仅反映历史样本窗口表现。 继续迭代换抽样起点和利润阈值,遍历训练集后最肥一笔验算收入 281 美元、系数 1.5。样本收齐后转 Go-Explore 训练,再用强化学习微调。 模型品质用训练集+测试集双查,关键看它能否吃下 2023 年 5 月第一周——这段不在训练集里,是训练期的直接延续,模型在那周历史数据上拿了利润,说明泛化不是纸上谈兵。

◍ 动态市况下模型要持续回训

我们用 4 个月历史数据完成了 Go-Explore 变体的首轮训练,靠优化后的采样机制,把原本跑不动的长周期数据也喂进了模型。训练和测试样本分流验证后,回测里策略信号的质量与稳定性都站住了。 外汇和贵金属属高风险市场,价格序列对突发宏观事件极敏感,再好的模型也不会一直有效。实盘前务必在 MT5 用近期行情重跑训练,观察样本外表现衰减速度,再决定回训频率。

「把探索类文章串成一条学习链」

想啃透强化学习里的艰难探索问题,单看一篇容易断片。原站有一组连续文章,从内在好奇心模块一路铺到 Go-Explore,正好构成一条技术递进链。 具体序号对应如下:先读「内在好奇心模块」打底,接着「关系强化学习」扩展表征,再到「分散关注度」优化策略分配,随后「凭借分歧进行自我监督探索」解决奖励稀疏,最后「Go-Explore,一种不同的探索方式」给出针对硬探索任务的新范式。 在 MT5 里验证这类思路时,建议先拿第 35 部分的 curiosity 信号做特征,再逐步叠加后续模块,观察回测中样本效率是否随模块增加而抬升。外汇与贵金属行情噪声大,这类实验务必用小仓位或模拟盘,失败概率不低。

随包附带的工程文件清单

这套 LSTM 多元时间序列预测方案不是单文件 EA,而是拆成了可组装的工程。随文压缩包 MQL5.zip(90.76 KB)里一共 7 个文件,两个阶段 EA 加一个优调 EA,其余四个是支撑类库与 OpenCL 内核。 Faza1.mq5 与 Faza2.mq5 分别是第一、第二阶段智能交易系统,GE-lerning.mq5 负责策略优调;Cell.mqh 定义系统状态结构,FQF.mqh 管完全参数化模型的工作排程,NeuroNet.mqh 封装神经网络创建逻辑,NeuroNet.cl 则是跑在 GPU 上的 OpenCL 代码库。想在 MT5 里复现,先把压缩包解到 MQL5/Files 或对应 Include 路径,缺一个 mqh 都会编译失败。 有读者在论坛反馈:Faza2 在把 Unsupervised 文件夹挪位前无法编译,说明 include 路径依赖很死;另一人用 4 个月数据训出约 1.2 GB、19 万特征的文件,第二阶段默认 10 万次迭代、甚至试到 1000 万次,误差仍卡在 1.6–1.8 不降,第三阶段 EA 只开仓不平仓。这类现象提示:直接照搬参数大概率跑不通,需自己调 lr(原设 3.0e-4f)和迭代节奏。外汇与贵金属市场高风险,任何模型输出都只是概率倾向,实盘前务必用策略测试器验证。

常见问题

先按时间窗和价格区间各做一次随机采样,取交集作为起点,再按固定步长执行,能避免单一随机导致的样本偏置。
用最短持仓寿命过滤噪音单,方向敞口只在波动率突破阈值时打开,平时保持中性,回测中可降约三成无效换手。
小布可定时拉取品种数据、触发回训任务并在市况漂移时推送提醒,你只需复核敞口开关参数即可。
日线级别样本建议每两周全量回训一次,小时线则按样本量累积到原集 20% 就增量回训,能跟上结构变化。
按「起点采样→步长执行→敞口逻辑→回训节奏」顺序精读,并对照随包工程文件逐模块跑通,比跳读省一半时间。