神经网络: 智能交易系统自我优化·进阶篇
🧠

神经网络: 智能交易系统自我优化·进阶篇

(2/3)· 定好策略只是起点,输入参数何时失效、怎样自动重训才是EA活下来的关键

偏理论 第 2/3 篇
很多交易者把EA跑起来就放任不管,以为一次优化管终身。市场状态漂移后旧权重仍在指挥下单,回撤往往来自这种沉默失效。本篇承接基础概念,直接拆解让EA自己按周期重训神经网络的实现布局。

「在MT5里搭一层可训练的神经网络壳」

想让价格行为模型跑在MT5本地,第一步是把多层感知机(MLP)的容器建出来。下面这段把学习率锁在0.001、训练循环只跑2次,属于轻量验证用的配置,真要上外汇或贵金属实盘前务必调参,这两类品种杠杆高、跳空频繁,过拟合会带来明显回撤风险。 double tasaAprende=0.001 决定每次权重更新的步长,ciclosEntren=2 表示整体样本只过两遍网络;若你拿EURUSD的M15收盘价做输入,2次循环往往连趋势毛刺都学不干净,仅适合先确认管道通不通。 creaRedNeuronal 里按隐藏层数量自动分派:两层隐藏都没设就走MLPCreate0(纯线性),只设第一层走MLPCreate1,两层都设才走MLPCreate2。建完用existeRed查nEntradas和nSalidas是否大于0,并打印层数、各层神经元数和权重总数,方便你直接对照日志看网络拓扑对不对。 别把0.001和2次当默认值焊死。打开MT5的EA向导把这段塞进OnInit,跑完看Experts标签里的「权重 Nº」输出,再决定要不要把ciclosEntren拉到50以上做严肃训练。

MQL5 / C++
class="type">class="kw">double tasaAprende= class="num">0.001;              class=class="str">"cmt">//网络训练比率
class="type">int ciclosEntren= class="num">2;                    class=class="str">"cmt">//训练循环次数
ResetLastError();
class="type">bool creada= creaRedNeuronal(objRed);    class=class="str">"cmt">//创建神经网络
 if(creada)
  {
    preparaDatosEntra(objRed, simb, arDatosAprende);    class=class="str">"cmt">//在 arDatosAprende 里下载输入/输出数据
    if(imprDatos) imprimeDatosEntra(simb, arDatosAprende);    class=class="str">"cmt">//显示数据以评估可信度
    if(normEntrada || normSalida) normalizaDatosRed(objRed, arDatosAprende, normEntrada, normSalida); class=class="str">"cmt">//输入/输出数据规范化选项
    if(barajar) barajaDatosEntra(arDatosAprende, nNeuronEntra+nNeuronSal);    class=class="str">"cmt">//遍历数据数组字符串
    errorMedioEntren= entrenaEvalRed(objRed, arDatosAprende, ciclosEntren, tasaAprende);    class=class="str">"cmt">//执行训练/优化
    salvaRedFich(arObjRed[codS], "copiaSegurRed_"+simb);    class=class="str">"cmt">//保存网络至磁盘文件
  }
  else infoError(GetLastError(), __FUNCTION__);

  class="kw">return(_LastError==class="num">0);
}
class=class="str">"cmt">//--------------------------------- 创建网络 --------------------------------------
class="type">bool creaRedNeuronal(CMultilayerPerceptronShell &objRed)
{
  class="type">bool creada= false;
  class="type">int nEntradas= class="num">0, nSalidas= class="num">0, nPesos= class="num">0;
  if(nNeuronCapa1<class="num">1 && nNeuronCapa2<class="num">1) CAlglib::MLPCreate0(nNeuronEntra, nNeuronSal, objRed);    class=class="str">"cmt">//线性输出
  else if(nNeuronCapa2<class="num">1) CAlglib::MLPCreate1(nNeuronEntra, nNeuronCapa1, nNeuronSal, objRed);    class=class="str">"cmt">//线性输出
  else CAlglib::MLPCreate2(nNeuronEntra, nNeuronCapa1, nNeuronCapa2, nNeuronSal, objRed);    class=class="str">"cmt">//线性输出
  creada= existeRed(objRed);
  if(!creada) Print("创建神经网络错误 ==> ", __FUNCTION__, " ", _LastError);
  else
  {
    CAlglib::MLPProperties(objRed, nEntradas, nSalidas, nPesos);
    Print("创建网络的层数 nº", propiedadRed(objRed, N_CAPAS));
    Print("输入层的神经元 Nº ", nEntradas);
    Print("隐藏层 class="num">1 的神经元 Nº ", nNeuronCapa1);
    Print("隐藏层 class="num">2 的神经元 Nº ", nNeuronCapa2);
    Print("输出层的神经元 Nº ", nSalidas);
    Print("权重 Nº", nPesos);
  }
  class="kw">return(creada);
}
class=class="str">"cmt">//--------------------------------- 存在网络 --------------------------------------------
class="type">bool existeRed(CMultilayerPerceptronShell &objRed)
{
  class="type">bool resp= false;
  class="type">int nEntradas= class="num">0, nSalidas= class="num">0, nPesos= class="num">0;
  CAlglib::MLPProperties(objRed, nEntradas, nSalidas, nPesos);
  resp= nEntradas>class="num">0 && nSalidas>class="num">0;
  class="kw">return(resp);
}
enum mis_PROPIEDADES_RED {N_CAPAS, N_NEURONAS, N_ENTRADAS, N_SALIDAS, N_PESOS};

◍ 把网络属性和训练数据塞进 MT5

做外汇或贵金属的 AI 辅助判断,第一步不是跑模型,而是先把多层感知机的结构和样本矩阵摸清楚。下面这个函数用枚举开关直接问网络:有几层、某层几个神经元、输入输出维度、总权重数,全部走 ALGLIB 封装,省得你手动维护。 int propiedadRed(CMultilayerPerceptronShell &objRed, mis_PROPIEDADES_RED prop=N_CAPAS, int numCapa=0) 里,prop 超过 N_NEURONAS 时才调 CAlglib::MLPProperties 拿全局参数;查单层神经元数必须传 numCapa,否则返回 0。这种懒加载在样本超过 5000 根 K 线时,能少调一次底层接口。 preparaDatosEntra 负责把指标值和策略结果写进 CMatrixDouble。arDatos.Resize(historialEntrena, nEntras+nSals) 决定了矩阵列数 = 输入神经元 + 输出神经元;外盘黄金 m5 上若 historialEntrena=1000、NUM_INDIC=5,矩阵就是 1000×7。 imprimeDatosEntra 把矩阵导成 dataEntrenaRed_符号.csv,表头写死 indic1;indic2;indic3...;resultEstrat。你开 MT5 文件目录找这个 csv,用 Excel 打开就能肉眼看哪根 bar 的特征异常——外汇杠杆高、贵金属跳空多,数据清洗前先盯这份表。 别把正态当圣经:导出的原始指标值若不做 normEntrada 归一,ALGLIB 在价差突变周可能训出偏移权重,回测好看实盘概率掉。

MQL5 / C++
class="type">int propiedadRed(CMultilayerPerceptronShell &objRed, mis_PROPIEDADES_RED prop= N_CAPAS, class="type">int numCapa= class="num">0)
{
  class=class="str">"cmt">//如果请求神经元数量 N_NEURONAS, 设置层索引 numCapa
  class="type">int resp= class="num">0, numEntras= class="num">0, numSals= class="num">0, numPesos= class="num">0;
  if(prop>N_NEURONAS) CAlglib::MLPProperties(objRed, numEntras, numSals, numPesos);
  class="kw">switch(prop)
  {
    case N_CAPAS:
      resp= CAlglib::MLPGetLayersCount(objRed);
      class="kw">break;
    case N_NEURONAS:
      resp= CAlglib::MLPGetLayerSize(objRed, numCapa);
      class="kw">break;
    case N_ENTRADAS:
      resp= numEntras;
      class="kw">break;
    case N_SALIDAS:
      resp= numSals;
      class="kw">break;
    case N_PESOS:
      resp= numPesos;
  }
  class="kw">return(resp);
}
class=class="str">"cmt">//---------------------------------- 准备输入/输出数据 --------------------------------------------------
class="type">void preparaDatosEntra(CMultilayerPerceptronShell &objRed, class="type">class="kw">string simb, CMatrixDouble &arDatos, class="type">bool normEntrada= true , class="type">bool normSalida= true)
{
  class="type">int fin= class="num">0, fila= class="num">0, colum= class="num">0,
      nEntras= propiedadRed(objRed, N_ENTRADAS),
      nSals= propiedadRed(objRed, N_SALIDAS);
  class="type">class="kw">double valor= class="num">0, arResp[];
  arDatos.Resize(historialEntrena, nEntras+nSals);
  fin= velaIniDesc+historialEntrena;
  for(fila= velaIniDesc; fila<fin; fila++)
  {
    for(colum= class="num">0; colum<NUM_INDIC;  colum++)
    {
      valor= valorIndic(codS, fila, colum);
      arDatos[fila-class="num">1].Set(colum, valor);
    }
    calcEstrat(fila-nVelasPredic, arResp);
    for(colum= class="num">0; colum<nSals; colum++) arDatos[fila-class="num">1].Set(colum+nEntras, arResp[colum]);
  }
  class="kw">return;
}
class=class="str">"cmt">//---------------------------------- 显示输入/输出数据 --------------------------------------------------
class="type">void imprimeDatosEntra(class="type">class="kw">string simb, CMatrixDouble &arDatos)
{
  class="type">class="kw">string encabeza= "indic1;indic2;indic3...;resultEstrat",  class=class="str">"cmt">//指标名称以 ";" 分隔
        fichImprime= "dataEntrenaRed_"+simb+".csv";
  class="type">bool entrar= false, copiado= false;
  class="type">int fila= class="num">0, colum= class="num">0, resultEstrat= -class="num">1, nBuff= class="num">0,
      nFilas= arDatos.Size(),
      nColum= nNeuronEntra+nNeuronSal,
      puntFich= FileOpen(fichImprime, FILE_WRITE|FILE_CSV|FILE_COMMON);
  FileWrite(puntFich, encabeza);
  for(fila= class="num">0; fila<nFilas; fila++)
  {
    linea= IntegerToString(fila)+";"+TimeToString(iTime(simb, PERIOD_CURRENT, velaIniDesc+fila), TIME_MINUTES)+";";
    for(colum= class="num">0; colum<nColum;  colum++)

把训练矩阵压进神经网络能吃的区间

写文件那段只是收尾:把二维数组 arDatos 按分号拼成行,DoubleToString 保留 8 位小数,FileWrite 落盘后 FileFlush+FileClose,弹窗告诉你文件在 TerminalInfoString(TERMINAL_COMMONDATA_PATH)+\"\\Files\"。真正喂给多层感知机前,得先过 normalizaDatosRed 这关。 规范化不是可选项。神经网络对量纲极敏感,欧元小时图收盘价和成交量差好几个数量级,直接塞进去梯度会崩。函数对输入列(前 nEntradas 列)和输出列(其后 nSalidas 列)分别扫一遍,先找每列 maxAbs/minAbs,再按 intervEntrada / intervSalida 里给的相对区间做线性映射。 注意输出列多存了两个全局量:minAbsSalida 和 maxAbsSalida。反归一化预测值时要用,漏掉就会把网络输出的 0~1 映射回原始量纲时整体偏移。外汇与贵金属杠杆高,信号偏差会被放大,验证时务必核对这两个值。 调用时 normEntrada 和 normSalida 默认都 true,若你只训输入不规输出(比如自编码器),显式传 false 省一轮扫描。StringSplit 用 ';' 拆区间字符串,格式必须是「下界;上界」两段,少一段 StringToDouble 会返回 0 把映射压扁。

MQL5 / C++
linea= linea+DoubleToString(arDatos[fila][colum], class="num">8)+(colum<(nColum-class="num">1)? ";": "");
FileWrite(puntFich, linea);
}
FileFlush(puntFich);
FileClose(puntFich);
Alert("下载文件= ", fichImprime);
Alert("路径= ", TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files");
class="kw">return;
}
class=class="str">"cmt">//------------------------------------ 规范化输入/输出数据-------------------------------------
class="type">void normalizaDatosRed(CMultilayerPerceptronShell &objRed, CMatrixDouble &arDatos, class="type">bool normEntrada= true, class="type">bool normSalida= true)
{
 class="type">int fila= class="num">0, colum= class="num">0, maxFila= arDatos.Size(),
 nEntradas= propiedadRed(objRed, N_ENTRADAS),
 nSalidas= propiedadRed(objRed, N_SALIDAS);
 class="type">class="kw">double maxAbs= class="num">0, minAbs= class="num">0, maxRel= class="num">0, minRel= class="num">0, arMaxMinRelEntra[], arMaxMinRelSals[];
 class="type">class="kw">ushort valCaract= StringGetCharacter(";", class="num">0);
 if(normEntrada) StringSplit(intervEntrada, valCaract, arMaxMinRelEntra);
 if(normSalida) StringSplit(intervSalida, valCaract, arMaxMinRelSals);
 for(colum= class="num">0; normEntrada && colum<nEntradas; colum++)
 {
 maxAbs= arDatos[class="num">0][colum];
 minAbs= arDatos[class="num">0][colum];
 minRel= StringToDouble(arMaxMinRelEntra[class="num">0]);
 maxRel= StringToDouble(arMaxMinRelEntra[class="num">1]);
 for(fila= class="num">0; fila<maxFila; fila++) class=class="str">"cmt">//定义每个数据列的 maxAbs 和 minAbs
 {
 if(maxAbs<arDatos[fila][colum]) maxAbs= arDatos[fila][colum];
 if(minAbs>arDatos[fila][colum]) minAbs= arDatos[fila][colum];
 }
 for(fila= class="num">0; fila<maxFila; fila++) class=class="str">"cmt">//设置新的规范化值
 arDatos[fila].Set(colum, normValor(arDatos[fila][colum], maxAbs, minAbs, maxRel, minRel));
 }
 for(colum= nEntradas; normSalida && colum<(nEntradas+nSalidas); colum++)
 {
 maxAbs= arDatos[class="num">0][colum];
 minAbs= arDatos[class="num">0][colum];
 minRel= StringToDouble(arMaxMinRelSals[class="num">0]);
 maxRel= StringToDouble(arMaxMinRelSals[class="num">1]);
 for(fila= class="num">0; fila<maxFila; fila++)
 {
 if(maxAbs<arDatos[fila][colum]) maxAbs= arDatos[fila][colum];
 if(minAbs>arDatos[fila][colum]) minAbs= arDatos[fila][colum];
 }
 minAbsSalida= minAbs;
 maxAbsSalida= maxAbs;
 for(fila= class="num">0; fila<maxFila; fila++)
 arDatos[fila].Set(colum, normValor(arDatos[fila][colum], maxAbs, minAbs, maxRel, minRel));
 }
 class="kw">return;
}

「把样本洗牌与LM训练接进MT5神经网络」

做外汇或贵金属模型训练时,数据顺序会带隐性偏置。下面这段先把训练矩阵按行随机打乱,再喂给多层感知机,属于可直接拷进EA工程的底层工具。 double normValor(...) 负责把任意数值线性映射到 [minRel, maxRel] 区间,默认 [-1,1];当 maxAbs<=minAbs 时返回0,调用前最好自己校验边界,否则归一化会静默失效。 barajaDatosEntra 用 MathSrand(GetTickCount()) 重置随机种子,再通过 randomEntero 选目标行做原地交换。注意它只推进 fila++ 当 filaDestino!=fila,若随机命中自身就重抽,最坏情况多耗几轮循环,但样本乱序是实打实完成的。 entrenaEvalRed 按权重规模分流:propiedadRed(objRed, N_PESOS)<500 走 MLPTrainLM,否则走 MLPTrainLBFGS(附带 0.01 的 Lipschitz 常数)。默认 tasaAprende=0.001、ciclosEntrena=2,小样本快速验证够用,实盘前建议把循环提到 50 以上观察误差曲线。 codResp 返回 2 或 6 才用 MLPRMSError 取平均误差;其余 code 只 Print 不报错,容易让上层以为训练成功。开 MT5 跑一遍,Alert 里的 NGrad / NHess 数量能直接看出优化器是否真在迭代。贵金属与外汇杠杆高,模型误差只是概率参考,别拿回测当仓位依据。

MQL5 / C++
class="type">class="kw">double normValor(class="type">class="kw">double valor, class="type">class="kw">double maxAbs, class="type">class="kw">double minAbs, class="type">class="kw">double maxRel= class="num">1, class="type">class="kw">double minRel= -class="num">1)
{
   class="type">class="kw">double valorNorm= class="num">0;
   if(maxAbs>minAbs) valorNorm= (valor-minAbs)*(maxRel-minRel))/(maxAbs-minAbs) + minRel;
   class="kw">return(valorNorm);
}
class=class="str">"cmt">//------------------------------------ 逐个遍历整个 输入/输出 字符串 -----------------------------------
class="type">void barajaDatosEntra(CMatrixDouble &arDatos, class="type">int nColum)
{
   class="type">int fila= class="num">0, colum= class="num">0, filaDestino= class="num">0, nFilas= arDatos.Size();
   class="type">class="kw">double filaTmp[];
   ArrayResize(filaTmp, nColum);
   MathSrand(GetTickCount());          class=class="str">"cmt">//重置随机后代序列
   class="kw">while(fila<nFilas)
   {
      filaDestino= randomEntero(class="num">0, nFilas-class="num">1);  class=class="str">"cmt">//以任意方式接收目标字符串
      if(filaDestino!=fila)
      {
         for(colum= class="num">0; colum<nColum; colum++) filaTmp[colum]= arDatos[filaDestino][colum];
         for(colum= class="num">0; colum<nColum; colum++) arDatos[filaDestino].Set(colum, arDatos[fila][colum]);
         for(colum= class="num">0; colum<nColum; colum++) arDatos[fila].Set(colum, filaTmp[colum]);
         fila++;
      }
   }
   class="kw">return;
}
class=class="str">"cmt">//---------------------------------- 随机移动 -------------------------------------------------------
class="type">int randomEntero(class="type">int minRel= class="num">0, class="type">int maxRel= class="num">1000)
{
   class="type">int num= (class="type">int)MathRound(randomDouble((class="type">class="kw">double)minRel, (class="type">class="kw">double)maxRel));
   class="kw">return(num);
}
class=class="str">"cmt">//---------------------------------- 网络训练-------------------------------------------
class="type">class="kw">double entrenaEvalRed(CMultilayerPerceptronShell &objRed, CMatrixDouble &arDatosEntrena, class="type">int ciclosEntrena= class="num">2, class="type">class="kw">double tasaAprende= class="num">0.001)
{
   class="type">bool salir= false;
   class="type">class="kw">double errorMedio= class="num">0; class="type">class="kw">string mens= "Entrenamiento Red";
   class="type">int k= class="num">0, i= class="num">0, codResp= class="num">0,
       historialEntrena= arDatosEntrena.Size();
   CMLPReportShell infoEntren;
   ResetLastError();
   class="type">class="kw">datetime tmpIni= TimeLocal();
   Alert("神经网络优化开始...");
   Alert("依据所应用的历史数据等待若干分钟。");
   Alert("...class=class="str">"cmt">///...");
   if(propiedadRed(objRed, N_PESOS)<class="num">500)
      CAlglib::MLPTrainLM(objRed, arDatosEntrena, historialEntrena, tasaAprende, ciclosEntrena, codResp, infoEntren);
   else
      CAlglib::MLPTrainLBFGS(objRed, arDatosEntrena, historialEntrena, tasaAprende, ciclosEntrena, class="num">0.01, class="num">0, codResp, infoEntren);
   if(codResp==class="num">2 || codResp==class="num">6) errorMedio= CAlglib::MLPRMSError(objRed, arDatosEntrena, historialEntrena);
   else Print("响应代码: ", codResp);
   class="type">class="kw">datetime tmpFin= TimeLocal();
   Alert("NGrad ", infoEntren.GetNGrad(), " NHess ", infoEntren.GetNHess(), " NCholesky ", infoEntren.GetNCholesky());
   Alert("codResp ", codResp," 平均训练误差 "+DoubleToString(errorMedio, class="num">8), " ciclosEntrena ", ciclosEntrena);

◍ 把训练好的多层感知机落盘成 .red

在 MT5 里跑完一轮神经网络训练,最怕 EA 重启后权重全丢。下面这段逻辑把 CMultilayerPerceptronShell 对象直接序列化到公共目录的二进制文件,默认名 copiaSegurRed.red,下次开机用 FileRead 逆向读回即可续用。

保存前先 FileDelete 同名的旧文件,再以 FILE_WRITEFILE_BINFILE_COMMON 打开句柄;句柄不等于 INVALID_HANDLE 才继续,否则 redSalvada 直接为 false。写入顺序很死板:先层数 numCapas,再逐层神经元规模 arNeurCapa[k],随后按层遍历每个神经元的缩放、激活函数类型与偏置。

输入层(k==0)和输出层(k==numCapas-1)要额外写 media 与 sigma,这两个值来自 MLPGetInputScaling / MLPGetOutputScaling,是 ALGLIB 做特征归一化的关键参数。中间隐藏层只写 funcTipo 和 umbral,连接权重通过 MLPGetWeight(objRed, k, i, k+1, j) 按「本层→下一层」的拓扑逐个落盘。 任何一步 FileWriteDouble 返回小于等于 0 都会让 redSalvada 链式置否,因此外层 for 都用 redSalvada && 做短路保护。外汇与贵金属行情具有高杠杆与跳空风险,这类模型权重仅反映历史样本分布,实盘加载后仍需用小资金验证泛化能力。

MQL5 / C++
class="type">bool salvaRedFich(CMultilayerPerceptronShell &objRed, class="type">class="kw">string nombArch= "")
{
   class="type">bool redSalvada= false;
   class="type">int k= class="num">0, i= class="num">0, j= class="num">0, numCapas= class="num">0, arNeurCapa[], neurCapa1= class="num">1, funcTipo= class="num">0, puntFichRed= class="num">9999;
   class="type">class="kw">double umbral= class="num">0, peso= class="num">0, media= class="num">0, sigma= class="num">0;
   if(nombArch=="") nombArch= "copiaSegurRed";
   nombArch= nombArch+".red";
   FileDelete(nombArch, FILE_COMMON);
   ResetLastError();
   puntFichRed= FileOpen(nombArch, FILE_WRITE|FILE_BIN|FILE_COMMON);
   redSalvada= puntFichRed!=INVALID_HANDLE;
   if(redSalvada)
   {
      numCapas= CAlglib::MLPGetLayersCount(objRed);
      redSalvada= redSalvada && FileWriteDouble(puntFichRed, numCapas)>class="num">0;
      ArrayResize(arNeurCapa, numCapas);
      for(k= class="num">0; redSalvada && k<numCapas; k++)
      {
         arNeurCapa[k]= CAlglib::MLPGetLayerSize(objRed, k);
         redSalvada= redSalvada && FileWriteDouble(puntFichRed, arNeurCapa[k])>class="num">0;
      }
      for(k= class="num">0; redSalvada && k<numCapas; k++)
      {
         for(i= class="num">0; redSalvada && i<arNeurCapa[k]; i++)
         {
            if(k==class="num">0)
            {
               CAlglib::MLPGetInputScaling(objRed, i, media, sigma);
               FileWriteDouble(puntFichRed, media);
               FileWriteDouble(puntFichRed, sigma);
            }
            else if(k==numCapas-class="num">1)
            {
               CAlglib::MLPGetOutputScaling(objRed, i, media, sigma);
               FileWriteDouble(puntFichRed, media);
               FileWriteDouble(puntFichRed, sigma);
            }
            CAlglib::MLPGetNeuronInfo(objRed, k, i, funcTipo, umbral);
            FileWriteDouble(puntFichRed, funcTipo);
            FileWriteDouble(puntFichRed, umbral);
            for(j= class="num">0; redSalvada && k<(numCapas-class="num">1) && j<arNeurCapa[k+class="num">1]; j++)
            {
               peso= CAlglib::MLPGetWeight(objRed, k, i, k+class="num">1, j);

从磁盘把训练好的网络读回来

把多层感知机落盘只是上半场,重启 EA 或换终端时还得把权重原样加载回内存。下面这段恢复函数默认读 copiaSegurRed.red,用 FILE_COMMON 标志保证文件在终端共享目录,换账户登录也能定位到。 恢复时先读层数 numCapas,按 2/3/4 层分别调 MLPCreate0/1/2 重建空网络壳子;若层数不在这三个区间,后面 existeRed 会返回 false 并打印 _LastError,网络等于没建起来。 重建后立刻用 MLPProperties 取回 nEntradas、nSalidas、nPesos,并把各层神经元数 Print 出来——这是验证读盘没串文件的直接手段。例如一层隐藏层结构会打出「隐藏层 1 的神经元 Nº」对应 nNeuronCapa1 的实际值。 输入层的缩放参数 media 与 sigma 存在文件头之后,按层、按神经元顺序 FileReadDouble 读回并 MLPSetInputScaling 写回。外汇与贵金属行情标准化若漏掉这一步,网络推理会概率性偏移,实盘信号可能失真。

MQL5 / C++
class="type">bool recuperaRedFich(CMultilayerPerceptronShell &objRed, class="type">class="kw">string nombArch= "")
{
   class="type">bool exito= false;
   class="type">int k= class="num">0, i= class="num">0, j= class="num">0, nEntradas= class="num">0, nSalidas= class="num">0, nPesos= class="num">0,
       numCapas= class="num">0, arNeurCapa[], funcTipo= class="num">0, puntFichRed= class="num">9999;
   class="type">class="kw">double umbral= class="num">0, peso= class="num">0, media= class="num">0, sigma= class="num">0;
   if(nombArch=="") nombArch= "copiaSegurRed";
   nombArch= nombArch+".red";
   puntFichRed= FileOpen(nombArch, FILE_READ|FILE_BIN|FILE_COMMON);
   exito= puntFichRed!=INVALID_HANDLE;
   if(exito)
   {
      numCapas= (class="type">int)FileReadDouble(puntFichRed);
      ArrayResize(arNeurCapa, numCapas);
      for(k= class="num">0; k<numCapas; k++) arNeurCapa[k]= (class="type">int)FileReadDouble(puntFichRed);
      if(numCapas==class="num">2) CAlglib::MLPCreate0(nNeuronEntra, nNeuronSal, objRed);
      else if(numCapas==class="num">3) CAlglib::MLPCreate1(nNeuronEntra, nNeuronCapa1, nNeuronSal, objRed);
      else if(numCapas==class="num">4) CAlglib::MLPCreate2(nNeuronEntra, nNeuronCapa1, nNeuronCapa2, nNeuronSal, objRed);
      exito= existeRed(arObjRed[class="num">0]);
      if(!exito) Print("神经网络生成错误 ==> ", __FUNCTION__, " ", _LastError);
      else
      {
         CAlglib::MLPProperties(objRed, nEntradas, nSalidas, nPesos);
         Print("恢复网络层数 nº", propiedadRed(objRed, N_CAPAS));
         Print("输入层的神经元 Nº ", nEntradas);
         Print("隐藏层 class="num">1 的神经元 Nº ", nNeuronCapa1);
         Print("隐藏层 class="num">2 的神经元 Nº ", nNeuronCapa2);
         Print("输出层的神经元 Nº ", nSalidas);
         Print("权重 Nº", nPesos);
         for(k= class="num">0; k<numCapas; k++)
         {
            for(i= class="num">0; i<arNeurCapa[k]; i++)
            {
               if(k==class="num">0)
               {
                  media= FileReadDouble(puntFichRed);
                  sigma= FileReadDouble(puntFichRed);
                  CAlglib::MLPSetInputScaling(objRed, i, media, sigma);
把重训调度交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到网络预测与重训间隔的偏离提示,你只需确认是否放宽周期,不必自己写OnTimer轮询。

常见问题

常见做法是接入ALGLIB的数值分析库,它提供跨语言的多层感知器训练接口,可在EA中直接调用做前向计算与权重更新。
按文中布局,预测值始终保留,步骤5不进入重复迭代,所以即使在优化窗口EA也依据上一次网络输出继续执行交易逻辑。
规范区间会影响梯度传播与初始权重收敛速度,倾向在[-1,1]下 sigmoid 类激活更对称,但具体依任务数据分布而定。
可以,小布盯盘的品种页内置了AIGC诊断,能提示当前预测偏移与下次重训倒计时,省去你手动翻日志。
数学任务有封闭解可做基准,能隔离网络结构错误与行情噪声,确认布局正确后再换数据输入模块去逼近交易准则更稳妥。