MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易·进阶篇
🗂️

MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易·进阶篇

(2/3)· 当交易品种和策略参数堆到上千个,MQL5输入限制会让优化变成翻列表的苦力活

实战向进阶 第 2/3 篇
把几十个品种的参数字段全塞进EA输入区,不仅逼近1024上限,每次改一个币种的止损都要在长列表里捞针。用外部txt承载参数、让测试器按编号读取,才是多币种规模化的正路。

◍ 逐行吃掉品种文件里的字符串

在 MT5 里批量扫多个交易品种时,常见做法是把品种名写进 TestedSymbols.txt,再用循环逐个读入内存数组。下面这段逻辑就是典型的文件行读取:只要没到行尾且 EA 没被停止,就持续 FileReadString 抓字符串,并用 FileTell 拿当前指针偏移。 注意一个细节:若 FileIsEnding 判定还没到文件末尾,偏移量要手动 offset++,再 FileSeek 以 SEEK_SET 重定位,否则可能卡在换行符上重复读。非空字符串才计入 strings_count,并通过 ArrayResize 动态扩数组。 读取函数返回行数后,InitializeInputSymbols 会调 ReadSymbolsFromFile("TestedSymbols.txt") 拿总条数。若处于优化模式、或回测/可视化模式下 SymbolNumber>0,则按 s==SymbolNumber-1 锁定本次要测的那个品种。外汇与贵金属波动剧烈、杠杆风险高,这种批量切换仅用于历史测试,实盘直接套用可能放大亏损概率。 别把正态当圣经:文件里少一行空行,strings_count 就可能比肉眼行数少 1,导致 SymbolNumber 越界报数组错误。开 MT5 写个 Print(strings_count) 验证最实在。

MQL5 / C++
while(!FileIsLineEnding(file_handle) || !IsStopped())
  {
   text=FileReadString(file_handle);
   offset=FileTell(file_handle);
   if(!FileIsEnding(file_handle))
      offset++;
   FileSeek(file_handle,offset,SEEK_SET);
   if(text!="")
     {
      strings_count++;
      ArrayResize(temporary_symbols,strings_count);
      temporary_symbols[strings_count-class="num">1]=text;
     }
   break;
  }
if(FileIsEnding(file_handle))
   break;
}
FileClose(file_handle);
}
class="kw">return(strings_count);
}
class="type">void InitializeInputSymbols()
  {
   class="type">int    strings_count=class="num">0;
   class="type">class="kw">string checked_symbol="";
   strings_count=ReadSymbolsFromFile("TestedSymbols.txt");
   if(IsOptimization() || ((IsTester() || IsVisualMode()) && SymbolNumber>class="num">0))
     {
      for(class="type">int s=class="num">0; s<strings_count; s++)
        {
         if(s==SymbolNumber-class="num">1)
           {
            class=class="str">"cmt">//--- 检查交易品种是否在交易服务器上

「回测与实盘下的品种装载分支」

这段逻辑解决一个很实际的问题:同一套 EA 在实盘、回测、可视化测试里,到底把哪些品种塞进 InputSymbols 数组。核心判断靠 IsTester()、IsVisualMode() 和 IsRealtime() 三个环境函数,不同分支写入方式完全不一样。 在测试或可视化模式下若 SymbolNumber==0 且走文件读取,会循环 temporary_symbols 逐个用 GetSymbolByName 校验服务器是否存在,存在的才 SYMBOLS_COUNT++ 并 ArrayResize 后写入。实测中若文件列了 20 个品种但服务器只挂了 17 个,最终数组长度就是 17,少掉的 3 个不会报错只会被静默跳过。 若测试模式走输入参数模式,或实盘 IsRealtime() 命中,都只装 1 个品种:前者写 Symbol() 当前图符号,后者同样写 Symbol()。注意测试分支里文件模式是累加计数从 0 开始,输入参数模式是直接写死 SYMBOLS_COUNT=1,两种写法混在同一函数里,改的时候容易漏。 外汇与贵金属品种在 MT5 服务器上可能临时不可交易,GetSymbolByName 返回空串就意味着该品种被过滤,回测前最好先打印一次 InputSymbols 长度确认装载符合预期。

MQL5 / C++
if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
  {
   class=class="str">"cmt">//--- 设置交易品种数量
   SYMBOLS_COUNT=class="num">1;
   class=class="str">"cmt">//--- 设置交易品种数组大小
   ArrayResize(InputSymbols,SYMBOLS_COUNT);
   class=class="str">"cmt">//--- 写下交易品种名称
   InputSymbols[class="num">0]=checked_symbol;
   }
class=class="str">"cmt">//--- 退出
class="kw">return;
   }
  }
class=class="str">"cmt">//--- 在测试或者可视化测试模式下,如果您需要测试文件列表里的全部交易品种
 if((IsTester() || IsVisualMode()) && SymbolNumber==class="num">0)
  {
   class=class="str">"cmt">//--- 参数读取方式: 从文件读取
   if(ParametersReadingMode==FILE)
     {
      class=class="str">"cmt">//--- 迭代文件中的所有交易品种
      for(class="type">int s=class="num">0; s<strings_count; s++)
        {
         class=class="str">"cmt">//--- 检查交易品种是否在交易服务器上
         if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
           {
            class=class="str">"cmt">//--- 增加交易品种计数器
            SYMBOLS_COUNT++;
            class=class="str">"cmt">//--- 设置交易品种数组大小
            ArrayResize(InputSymbols,SYMBOLS_COUNT);
            class=class="str">"cmt">//--- 写下交易品种名称
            InputSymbols[SYMBOLS_COUNT-class="num">1]=checked_symbol;
           }
        }
      class=class="str">"cmt">//--- 退出
      class="kw">return;
     }
   class=class="str">"cmt">//--- 参数读取模式: 从EA交易的输入参数中读取
   if(ParametersReadingMode==INPUT_PARAMETERS)
     {
      class=class="str">"cmt">//--- 设置交易品种数量
      SYMBOLS_COUNT=class="num">1;
      class=class="str">"cmt">//--- 设置交易品种数组大小
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      class=class="str">"cmt">//--- 写下当前交易品种名称
      InputSymbols[class="num">0]=Symbol();
      class=class="str">"cmt">//--- 退出
      class="kw">return;
     }
  }
class=class="str">"cmt">//--- 在EA交易的正常运行模式下,使用当前图表的交易品种
 if(IsRealtime())
  {
   class=class="str">"cmt">//--- 设置交易品种数量
   SYMBOLS_COUNT=class="num">1;
   class=class="str">"cmt">//--- 设置交易品种数组大小
   ArrayResize(InputSymbols,SYMBOLS_COUNT);
   class=class="str">"cmt">//--- 写下交易品种名称
   InputSymbols[class="num">0]=Symbol();
  }

多品种参数数组的批量扩容与文件读取

做跨品种 EA 时,输入参数不能写死成单个变量。下面这段先把 8 类参数数组按 SYMBOLS_COUNT 一次性扩容,保证后面按品种下标存值不会越界。 void ResizeInputParametersArrays() { ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT); ArrayResize(InputTakeProfit,SYMBOLS_COUNT); ArrayResize(InputStopLoss,SYMBOLS_COUNT); ArrayResize(InputTrailingStop,SYMBOLS_COUNT); ArrayResize(InputReverse,SYMBOLS_COUNT); ArrayResize(InputLot,SYMBOLS_COUNT); ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT); ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT); } 逐行看:函数入口无返回值,仅做内存预分配;8 次 ArrayResize 分别对应指标周期、止盈、止损、追踪止损、反向开关、手数、加仓量、加仓步长,目标长度都是 SYMBOLS_COUNT。若你实际监控 12 个货币对,SYMBOLS_COUNT 设为 12,这些数组就各占 12 个双精度槽位。 参数落盘后靠文本文件回读。ReadInputParametersValuesFromFile 接收文件句柄和 double 引用数组,按 key=value 格式逐行解析。 bool ReadInputParametersValuesFromFile(int handle,double &array[]) { int delimiter_position =0; // 字符串中 "=" 字符的位置 int strings_count =0; // 字符串计数器 string read_string =""; // 读取的字符串 ulong offset =0; // 指针的位置 (偏移字节数) FileSeek(handle,0,SEEK_SET); while(!FileIsEnding(handle)) { if(IsStopped()) return(false); while(!FileIsLineEnding(handle)) { if(IsStopped()) return(false); read_string=FileReadString(handle); delimiter_position=StringFind(read_string,"=",0); read_string=StringSubstr(read_string,delimiter_position+1); array[strings_count]=StringToDouble(read_string); offset=FileTell(handle); if(FileIsLineEnding(handle)) { if(!FileIsEnding(handle)) offset++; FileSeek(handle,offset,SEEK_SET); 逻辑拆解:开头 FileSeek 把指针归零;外层 while 跑到文件尾,内层 while 按行读,两次 IsStopped 检查保证用户在 MT5 里移除 EA 时能立即退出,不卡文件 IO。StringFind 定位等号,StringSubstr 截取值部分,StringToDouble 转成数值写进 array[strings_count],计数器随行递增。注意 FileTell 取当前偏移,遇行尾若非文件尾则 offset++ 跳过换行符再 Seek 回去——这套手动指针管理在 MT5 文件函数里很容易因多一个空行而错位,实盘前建议用 3~5 行样例文件在策略测试器里单步验证。外汇与贵金属杠杆交易高风险,参数文件解析异常可能直接导致下单手数为 0 或越界,须先在模拟环境跑通。

MQL5 / C++
class="type">void ResizeInputParametersArrays()
  {
   ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT);
   ArrayResize(InputTakeProfit,SYMBOLS_COUNT);
   ArrayResize(InputStopLoss,SYMBOLS_COUNT);
   ArrayResize(InputTrailingStop,SYMBOLS_COUNT);
   ArrayResize(InputReverse,SYMBOLS_COUNT);
   ArrayResize(InputLot,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT);
  }

class="type">bool ReadInputParametersValuesFromFile(class="type">int handle,class="type">class="kw">double &array[])
  {
   class="type">int    delimiter_position  =class="num">0;  class=class="str">"cmt">// 字符串中 "=" 字符的位置
   class="type">int    strings_count       =class="num">0;  class=class="str">"cmt">// 字符串计数器
   class="type">class="kw">string read_string         =""; class=class="str">"cmt">// 读取的字符串
   class="type">class="kw">ulong  offset              =class="num">0;  class=class="str">"cmt">// 指针的位置 (偏移字节数)
   FileSeek(handle,class="num">0,SEEK_SET);
   while(!FileIsEnding(handle))
     {
      if(IsStopped())
       class="kw">return(class="kw">false);
      while(!FileIsLineEnding(handle))
       {
        if(IsStopped())
         class="kw">return(class="kw">false);
        read_string=FileReadString(handle);
        delimiter_position=StringFind(read_string,"=",class="num">0);
        read_string=StringSubstr(read_string,delimiter_position+class="num">1);
        array[strings_count]=StringToDouble(read_string);
        offset=FileTell(handle);
        if(FileIsLineEnding(handle))
          {
           if(!FileIsEnding(handle))
            offset++;
           FileSeek(handle,offset,SEEK_SET);

◍ 正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;strings_count++; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- 退出嵌套的读取字符串的循环</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- 如果是文件末尾,退出主循环</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(<span class="functions">FileIsEnding</span>(handle)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp; } <span class="comment">//--- 返回成功结束的事实</span> &nbsp;&nbsp; <span class="keyword">return</span>(<span class="macro">true</span>); &nbsp;&nbsp;} input_parameter_name1=value input_parameter_name2=value .... input_parameter_nameN=value <span class="comment">//+------------------------------------------------------------------+</span>

<span class="comment">//为一个交易品种从文件读取输入参数或写输入参数到文件中&nbsp;&nbsp; &nbsp;&nbsp;</span>

<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> ReadWriteInputParameters(<span class="keyword">int</span> symbol_number,<span class="keyword">string</span> path) &nbsp;&nbsp;{ &nbsp;&nbsp; <span class="keyword">string</span> file_nam

「把优化结果落盘成每品种独立 ini」

EA 在优化器跑完一轮后,会把 8 个关键输入(指标周期、止盈、止损、追踪、反转开关、手数、加仓与加仓步长)按顺序写进以品种命名的 .ini 文件,方便后续按符号加载。写入用的是 FILE_COMMON 标志,意味着文件落在终端共享目录,多账户副本都能读到,路径隔离靠文件名本身。 下面这段是写文件与回填数组的核心。句柄有效才进循环,分隔符用 '=' 纯文本可读,FileWrite 与 Print 同步输出,便于在日志里即时核对参数映射有没有串号。 回填时下标是硬绑定:tested_parameters_values[0] 一定是指标周期、[4] 强转 bool 当反转开关、[5] 是手数。若你改了输入顺序,这里不跟着改就会把止损填进手数字段,回测直接变形。外汇与贵金属杠杆高,参数错配可能在实盘触发非预期仓位,先用策略测试器跑一遍打印核对再上。 CreateInputParametersFolder 只负责在共享目录建 'EA名\\' 根目录,search_handle 初始为 INVALID_HANDLE,返回的文件夹路径供上层拼具体品种 ini 全路径。文件夹不存在就建,存在就跳过,避免每次优化都抛文件错误。

MQL5 / C++
class="type">int file_handle_write=FileOpen(file_name,FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,"");
class=class="str">"cmt">//--- 如果已经获得了句柄
if(file_handle_write!=INVALID_HANDLE)
  {
   class="type">class="kw">string delimiter="="; class=class="str">"cmt">// 分隔符号
   class=class="str">"cmt">//--- 写入参数
   for(class="type">int i=class="num">0; i<TESTED_PARAMETERS_COUNT; i++)
     {
      FileWrite(file_handle_write,input_parameters_names[i],delimiter,tested_parameters_values[i]);
      Print(input_parameters_names[i],delimiter,tested_parameters_values[i]);
     }
   class=class="str">"cmt">//--- 把参数值写到数组中
   InputIndicatorPeriod[symbol_number]    =(class="type">int)tested_parameters_values[class="num">0];
   InputTakeProfit[symbol_number]         =tested_parameters_values[class="num">1];
   InputStopLoss[symbol_number]           =tested_parameters_values[class="num">2];
   InputTrailingStop[symbol_number]       =tested_parameters_values[class="num">3];
   InputReverse[symbol_number]            =(class="type">bool)tested_parameters_values[class="num">4];
   InputLot[symbol_number]                =tested_parameters_values[class="num">5];
   InputVolumeIncrease[symbol_number]     =tested_parameters_values[class="num">6];
   InputVolumeIncreaseStep[symbol_number] =tested_parameters_values[class="num">7];
   class=class="str">"cmt">//--- 根据指向打印相关信息
   if(RewriteParameters)
      Print("文件 &class="macro">#x27;"+InputSymbols[symbol_number]+".ini&class="macro">#x27; 参数 &class="macro">#x27;"+EXPERT_NAME+".ex5 EA交易已经被重写&class="macro">#x27;");
   else
      Print("文件 &class="macro">#x27;"+InputSymbols[symbol_number]+".ini&class="macro">#x27; 参数 &class="macro">#x27;"+EXPERT_NAME+".ex5 EA交易已经被创建&class="macro">#x27;");
   }
class=class="str">"cmt">//--- 关闭写入文件句柄
FileClose(file_handle_write);
 }
}
class=class="str">"cmt">//+----------------------------------------------------------------------------------+
class=class="str">"cmt">//| 如果包含输入参数的文件夹不存在则创建文件夹                                             |
class=class="str">"cmt">//| 如果成功则返回文件夹路径                                                                                    |
class=class="str">"cmt">//+----------------------------------------------------------------------------------+
class="type">class="kw">string CreateInputParametersFolder()
  {
   class="type">long   search_handle       =INVALID_HANDLE;   class=class="str">"cmt">// 搜索文件夹/文件的句柄
   class="type">class="kw">string EA_root_folder      =EXPERT_NAME+"\\"; class=class="str">"cmt">// EA交易的根文件夹
   class="type">class="kw">string returned_filename   ="";               class=class="str">"cmt">// 找到的对象的名称(文件/文件夹)
   class="type">class="kw">string search_path         ="";               class=class="str">"cmt">// 搜索路径
把参数巡检交给小布
这些多币种参数的读写诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到哪些配置文件和当前实盘设置产生了偏移,你只管决策要不要重跑优化。

常见问题

客户端对EA输入参数总数有硬限制,超出后编译器会报错,且超长列表在终端里难以维护和逐条优化。
会读取文本列表里的全部交易品种,若数值超过列表长度则对应编号无品种可测、该次跳过。
可以,小布盯盘内置的AIGC会比对配置文件与盘面实际参数,提示偏离项,省去人工翻txt核对。
会对SymbolNumber指定编号的品种写入当前EA参数值,反向为false时才从文件载入,误操作前先确认编号。
目前测试器无法直接访问报价窗口,需用脚本或手工导出txt,EA再按文件取列表,这是本篇模式的前提。