神经网络实验(第 2 部分):智能神经网络优化·进阶篇
📘

神经网络实验(第 2 部分):智能神经网络优化·进阶篇

第 2/3 篇

「区间随机值的字符串补位处理」

在 MT5 脚本里批量生成模拟报价时,常用 RNDfromCI(Min, Max) 取置信区间随机值,再用 DoubleToString(...,1) 统一留 1 位小数。上面这段连续给 sx2~sx11、ss0~ss2 赋值,就是一次性铺了 13 个带 1 位小数格式的字符串变量。 但 DoubleToString 在数值恰好为整数时可能不显小数点,比如返回 "7" 而非 "7.0"。下面的 if 判断用 StringFind(变量,".",0)==-1 检测缺小数点,缺了就手动拼 ".0"。sw0~sw15、sb0~sb3、sx0 都走了这套补位,保证后续拼接的报表或面板每一格宽度对齐。 外汇与贵金属模拟数据高风险,这套补位只解决显示一致性,不改变随机分布本身。开 MT5 把这段代码塞进 OnInit 跑一遍,打印 sx2 和 sw0,看整数情形是否真补出 .0。

MQL5 / C++
  sx2=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx3=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx4=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx5=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx6=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx7=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx8=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx9=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx10=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  sx11=DoubleToString(RNDfromCI(Min, Max),class="num">1);

  ss0=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  ss1=DoubleToString(RNDfromCI(Min, Max),class="num">1);
  ss2=DoubleToString(RNDfromCI(Min, Max),class="num">1);

  if(StringFind(sw0,".", class="num">0) == -class="num">1 ){sw0=sw0+".class="num">0";}
  if(StringFind(sw1,".", class="num">0) == -class="num">1 ){sw1=sw1+".class="num">0";}
  if(StringFind(sw2,".", class="num">0) == -class="num">1 ){sw2=sw2+".class="num">0";}
  if(StringFind(sw3,".", class="num">0) == -class="num">1 ){sw3=sw3+".class="num">0";}
  if(StringFind(sw4,".", class="num">0) == -class="num">1 ){sw4=sw4+".class="num">0";}
  if(StringFind(sw5,".", class="num">0) == -class="num">1 ){sw5=sw5+".class="num">0";}
  if(StringFind(sw6,".", class="num">0) == -class="num">1 ){sw6=sw6+".class="num">0";}
  if(StringFind(sw7,".", class="num">0) == -class="num">1 ){sw7=sw7+".class="num">0";}
  if(StringFind(sw8,".", class="num">0) == -class="num">1 ){sw8=sw8+".class="num">0";}
  if(StringFind(sw9,".", class="num">0) == -class="num">1 ){sw9=sw9+".class="num">0";}
  if(StringFind(sw10,".", class="num">0) == -class="num">1 ){sw10=sw10+".class="num">0";}
  if(StringFind(sw11,".", class="num">0) == -class="num">1 ){sw11=sw11+".class="num">0";}
  if(StringFind(sw12,".", class="num">0) == -class="num">1 ){sw12=sw12+".class="num">0";}
  if(StringFind(sw13,".", class="num">0) == -class="num">1 ){sw13=sw13+".class="num">0";}
  if(StringFind(sw14,".", class="num">0) == -class="num">1 ){sw14=sw14+".class="num">0";}
  if(StringFind(sw15,".", class="num">0) == -class="num">1 ){sw15=sw15+".class="num">0";}
  if(StringFind(sb0,".", class="num">0) == -class="num">1 ){sb0=sb0+".class="num">0";}
  if(StringFind(sb1,".", class="num">0) == -class="num">1 ){sb1=sb1+".class="num">0";}
  if(StringFind(sb2,".", class="num">0) == -class="num">1 ){sb2=sb2+".class="num">0";}
  if(StringFind(sb3,".", class="num">0) == -class="num">1 ){sb3=sb3+".class="num">0";}

  if(StringFind(sx0,".", class="num">0) == -class="num">1 ){sx0=sx0+".class="num">0";}

补齐小数位再落盘权重

这段逻辑干的事很直白:把 11 个 sx 系字符串和 3 个 ss 系字符串先过一遍,若里头找不到小数点(StringFind 返回 -1),就手动拼上 .0。外汇与贵金属参数序列化时,缺小数点会被 StringToDouble 当成整数处理,权重计算可能直接偏掉一格,属于典型的高风险静默 bug。

补齐后把所有字段用逗号拼成 str,交给 VerifyPassagesInFile 做一遍校验。只有返回 true,才允许用 FileOpen 以 FILE_WRITEFILE_READFILE_CSVFILE_COMMONFILE_ANSI 打开共享目录下的优化文件,并 Seek 到尾部追加写入 38 个列(sw0~sw15、sb0~sb3、sx0~sx11、ss0~ss2)。

写盘成功紧接着把 sw0 到 sw11 共 12 个权重字符串用 StringToDouble 灌进 weight[0]~weight[11]。在 MT5 里跑这类 EA 时,可以故意把某个 sx 传成纯整数,观察文件里是否补了 .0,以此验证这段防护是否生效。

MQL5 / C++
if(StringFind(sx1,".", class="num">0) == -class="num">1 ){sx1=sx1+".class="num">0";}
if(StringFind(sx2,".", class="num">0) == -class="num">1 ){sx2=sx2+".class="num">0";}
if(StringFind(sx3,".", class="num">0) == -class="num">1 ){sx3=sx3+".class="num">0";}
if(StringFind(sx4,".", class="num">0) == -class="num">1 ){sx4=sx4+".class="num">0";}
if(StringFind(sx5,".", class="num">0) == -class="num">1 ){sx5=sx5+".class="num">0";}
if(StringFind(sx6,".", class="num">0) == -class="num">1 ){sx6=sx6+".class="num">0";}
if(StringFind(sx7,".", class="num">0) == -class="num">1 ){sx7=sx7+".class="num">0";}
if(StringFind(sx8,".", class="num">0) == -class="num">1 ){sx8=sx8+".class="num">0";}
if(StringFind(sx9,".", class="num">0) == -class="num">1 ){sx9=sx9+".class="num">0";}
if(StringFind(sx10,".", class="num">0) == -class="num">1 ){sx10=sx10+".class="num">0";}
if(StringFind(sx11,".", class="num">0) == -class="num">1 ){sx11=sx11+".class="num">0";}

if(StringFind(ss0,".", class="num">0) == -class="num">1 ){ss0=ss0+".class="num">0";}
if(StringFind(ss1,".", class="num">0) == -class="num">1 ){ss1=ss1+".class="num">0";}
if(StringFind(ss2,".", class="num">0) == -class="num">1 ){ss2=ss2+".class="num">0";}

str=sw0+","+sw1+","+sw2+","+sw3+","+sw4+","+sw5+","+sw6+","+sw7+","+sw8+","+sw9+","+sw10+","+sw11+","+sw12+","+sw13+","+sw14+","+sw15
+","+sb0+","+sb1+","+sb2+","+sb3
+","+sx0+","+sx1+","+sx2+","+sx3+","+sx4+","+sx5+","+sx6+","+sx7+","+sx8+","+sx9+","+sx10+","+sx11
+","+ss0+","+ss1+","+ss2;
if (VerifyPassagesInFile(str)==true)
{
    ResetLastError();
    filehandle = FileOpen(OptimizationFileName,FILE_WRITE|FILE_READ|FILE_CSV|FILE_COMMON|FILE_ANSI, ";");
    if(filehandle!=INVALID_HANDLE)
    {
        Print("FileOpen OK");
        FileSeek(filehandle, class="num">0, SEEK_END);
        FileWrite(filehandle,Passages,sw0,sw1,sw2,sw3,sw4,sw5,sw6,sw7,sw8,sw9,sw10,sw11,sw12,sw13,sw14,sw15,
        sb0,sb1,sb2,sb3,
        sx0,sx1,sx2,sx3,sx4,sx5,sx6,sx7,sx8,sx9,sx10,sx11,
        ss0,ss1,ss2);
        FileClose(filehandle);
        
 weight[class="num">0]=StringToDouble(sw0);
 weight[class="num">1]=StringToDouble(sw1);
 weight[class="num">2]=StringToDouble(sw2);
 weight[class="num">3]=StringToDouble(sw3);
 weight[class="num">4]=StringToDouble(sw4);
 weight[class="num">5]=StringToDouble(sw5);
 weight[class="num">6]=StringToDouble(sw6);
 weight[class="num">7]=StringToDouble(sw7);
 weight[class="num">8]=StringToDouble(sw8);
 weight[class="num">9]=StringToDouble(sw9);
 weight[class="num">10]=StringToDouble(sw10);
 weight[class="num">11]=StringToDouble(sw11);

◍ 权重数组回填与文件校验的衔接点

这段代码片段在做两件事:先把外部读入的字符串变量逐一并转换成 double,写进 weight[12] 到 weight[34] 的连续槽位;随后用 r、q 两个标志位控制读取成功与否。 从下标看,weight[12..15] 对应 sw 系列 4 个参数,weight[16..19] 是 sb 系列 4 个,weight[20..31] 是 sx 系列 12 个,weight[32..34] 是 ss 系列 3 个,合计 23 个权重值被灌入数组。若文件打开失败,Print 打出 FileOpen ERROR 及 GetLastError() 码,并把 q 置 0,调用方应据此跳过后续优化逻辑。 紧接着的 VerifyPassagesInFile 函数预声明了 str 到 str36 共 37 个 string 局部变量,并在入口用 FileIsExist(OptimizationFileName) 判断配置文件是否真实存在——这一步是防止脏读的必要栅栏。开 MT5 把这段贴进 EA,改 OptimizationFileName 路径跑一次,就能看到 q=0 的报错分支在什么环境下触发。外汇与贵金属品种下此类文件 IO 受限于终端沙箱目录,路径写错会直接走 else 分支,属高风险配置环节。

MQL5 / C++
  weight[class="num">12]=StringToDouble(sw12);
  weight[class="num">13]=StringToDouble(sw13);
  weight[class="num">14]=StringToDouble(sw14);
  weight[class="num">15]=StringToDouble(sw15);

  weight[class="num">16]=StringToDouble(sb0);
  weight[class="num">17]=StringToDouble(sb1);
  weight[class="num">18]=StringToDouble(sb2);
  weight[class="num">19]=StringToDouble(sb3);

  weight[class="num">20]=StringToDouble(sx0);
  weight[class="num">21]=StringToDouble(sx1);
  weight[class="num">22]=StringToDouble(sx2);
  weight[class="num">23]=StringToDouble(sx3);
  weight[class="num">24]=StringToDouble(sx4);
  weight[class="num">25]=StringToDouble(sx5);
  weight[class="num">26]=StringToDouble(sx6);
  weight[class="num">27]=StringToDouble(sx7);
  weight[class="num">28]=StringToDouble(sx8);
  weight[class="num">29]=StringToDouble(sx9);
  weight[class="num">30]=StringToDouble(sx10);
  weight[class="num">31]=StringToDouble(sx11);

  weight[class="num">32]=StringToDouble(ss0);
  weight[class="num">33]=StringToDouble(ss1);
  weight[class="num">34]=StringToDouble(ss2);

                    r=class="num">1;
                    q=class="num">1;
     }
     else
     {
     Print("FileOpen ERROR, error ",GetLastError());
     q=class="num">0;
     }
  }
}
}
class="type">bool VerifyPassagesInFile(class="type">class="kw">string st){
class="type">class="kw">string str="";
class="type">class="kw">string str1="";
class="type">class="kw">string str2="";
class="type">class="kw">string str3="";
class="type">class="kw">string str4="";
class="type">class="kw">string str5="";
class="type">class="kw">string str6="";
class="type">class="kw">string str7="";
class="type">class="kw">string str8="";
class="type">class="kw">string str9="";
class="type">class="kw">string str10="";
class="type">class="kw">string str11="";
class="type">class="kw">string str12="";
class="type">class="kw">string str13="";
class="type">class="kw">string str14="";
class="type">class="kw">string str15="";
class="type">class="kw">string str16="";
class="type">class="kw">string str17="";
class="type">class="kw">string str18="";
class="type">class="kw">string str19="";
class="type">class="kw">string str20="";
class="type">class="kw">string str21="";
class="type">class="kw">string str22="";
class="type">class="kw">string str23="";
class="type">class="kw">string str24="";
class="type">class="kw">string str25="";
class="type">class="kw">string str26="";
class="type">class="kw">string str27="";
class="type">class="kw">string str28="";
class="type">class="kw">string str29="";
class="type">class="kw">string str30="";
class="type">class="kw">string str31="";
class="type">class="kw">string str32="";
class="type">class="kw">string str33="";
class="type">class="kw">string str34="";
class="type">class="kw">string str35="";
class="type">class="kw">string str36="";
if (FileIsExist(OptimizationFileName)==
true ){

「用 CSV 共享区存取优化参数」

EA 在 MT5 里跨品种传递优化结果,常见做法是落盘到公共目录的 CSV。先用 ResetLastError 清掉线程级错误码,再以 FILE_COMMON 标记把文件开在终端共享区,这样不同图表上的实例都能读到同一份参数表。 下面这段代码演示了打开与读取两个动作:写模式打开若拿到 INVALID_HANDLE,就打印 GetLastError 的具体码;读模式打开成功后进入逐行解析。FILE_CSV 配合分号分隔,意味着每列是一次优化出的某个输入变量。 读取循环里一口气 FileReadString 了 36 次,对应一行里 36 个字段。str1 通常是标识列,真正拼回参数字符串时从 str2 起用逗号连到 str21,说明这张表头 21 个有效参数、其余列可能留作备注或版本号。外汇与贵金属品种做这种共享优化请留意:跨实例读写若碰上并发,文件锁缺失可能导致某次读取拿到半行,概率上会引发参数错位。 验证方式很直接:把 OptimizationFileName 指向 Common\Files 下的自定义名,在策略测试器跑完优化后让 EA 写一次,再开第二个 EURUSD 实例看是否打印 FileOpen OK 并读满 36 列。

MQL5 / C++
  ResetLastError();
  filehandle = FileOpen(OptimizationFileName,FILE_WRITE|FILE_READ|FILE_CSV|FILE_COMMON|FILE_ANSI, ";");
  if(filehandle!=INVALID_HANDLE)
    {
      Print("FileCreate OK");
    }
  else Print("FileCreate ERROR, error ",GetLastError());
  FileClose(filehandle);
}
  ResetLastError();
  filehandle = FileOpen(OptimizationFileName,FILE_WRITE|FILE_READ|FILE_CSV|FILE_COMMON|FILE_ANSI, ";");
  if(filehandle!=INVALID_HANDLE)
    {
      Print("FileOpen OK");
    }
  else Print("FileOpen ERROR, error ",GetLastError());

class=class="str">"cmt">//--- read the data from the file
class="kw">while(!FileIsEnding(filehandle) && !IsStopped())
{
  str1=FileReadString(filehandle);
  str2=FileReadString(filehandle);
  str3=FileReadString(filehandle);
  str4=FileReadString(filehandle);
  str5=FileReadString(filehandle);
  str6=FileReadString(filehandle);
  str7=FileReadString(filehandle);
  str8=FileReadString(filehandle);
  str9=FileReadString(filehandle);
  str10=FileReadString(filehandle);
  str11=FileReadString(filehandle);
  str12=FileReadString(filehandle);
  str13=FileReadString(filehandle);
  str14=FileReadString(filehandle);
  str15=FileReadString(filehandle);
  str16=FileReadString(filehandle);
  str17=FileReadString(filehandle);
  str18=FileReadString(filehandle);
  str19=FileReadString(filehandle);
  str20=FileReadString(filehandle);
  str21=FileReadString(filehandle);
  str22=FileReadString(filehandle);
  str23=FileReadString(filehandle);
  str24=FileReadString(filehandle);
  str25=FileReadString(filehandle);
  str26=FileReadString(filehandle);
  str27=FileReadString(filehandle);
  str28=FileReadString(filehandle);
  str29=FileReadString(filehandle);
  str30=FileReadString(filehandle);
  str31=FileReadString(filehandle);
  str32=FileReadString(filehandle);
  str33=FileReadString(filehandle);
  str34=FileReadString(filehandle);
  str35=FileReadString(filehandle);
  str36=FileReadString(filehandle);
  
  str=str2+","+str3+","+str4+","+str5+","+str6+","+str7+","+str8+","+str9+","+str10+","+str11
  +","+str12+","+str13+","+str14+","+str15+","+str16+","+str17+","+str18+","+str19+","+str20+","+str21

写文件前的重复校验与收尾

这段逻辑出现在指标把 36 个字符串变量拼接成一行 CSV 内容的末尾。str22 到 str36 依次用逗号连接,补全了前面未列出的字段,最终得到完整行串 str。 紧接着用 if(str==st) 做一道防护:若新拼出的行与已有内容 st 完全一致,说明这次写入是重复数据,直接 FileClose 并 return(false),避免历史记录被刷重复。 若校验通过,函数走到最外层 FileClose(filehandle) 后 return(true),告知调用方写入成功。外汇与贵金属行情跳动频繁,这类去重判断能显著降低日志文件膨胀,但高频品种仍建议手动定期清理,相关操作属高风险环境下的工程细节,请自行在 MT5 策略测试器验证行为。

MQL5 / C++
   +","+str22+","+str23+","+str24+","+str25+","+str26+","+str27+","+str28+","+str29+","+str30+","+str31+","+str32+","+str33+","+str34+","+str35+","+str36;
   if(str==st){FileClose(filehandle); class="kw">return(false);}
}
FileClose(filehandle);
class="kw">return(true);
}

常见问题

在写文件前把每个随机值格式化为固定小数位字符串(如补零到 6 位),避免后续读取错位。
在文件末尾写校验和或行数标记,加载时先比对标记再回填,不一致就丢弃重算。
可以,小布能按你的品种页配置自动读写共享区 CSV 并做落盘前重复校验,你只看差异结果。
多是写文件时没收尾换行或重复写入未清空,写前先截断文件、写完补单行结尾即可。
查参数是否越界、与上一轮是否完全一致、字符串补位后长度是否合规,三项过了再落盘。