MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易·综合运用
📘

MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易·综合运用

第 3/3 篇

在通用目录里定位并自建EA根文件夹

MT5 的 FileFindFirst 配合 FILE_COMMON 标志,能在终端的通用数据目录下做文件/文件夹检索,而不是局限在单个品种的数据夹里。这段代码先以通配符 * 搜一遍,判断名为 EA_root_folder 的 EA 根目录是否已经存在;若首条返回就不是它,就靠 FileFindNext 继续迭代,直到命中或用户用 IsStopped() 中止。 如果搜完发现根目录缺失,代码会调用 FolderCreate(EXPERT_NAME, FILE_COMMON) 在通用目录里建一层以 EA 名命名的文件夹,建成功后把 is_root_folder 置真并打印确认。若创建失败则直接返回空串,避免后续往不存在的路径写参数。 整个函数最终要么返回 'EXPERT_NAME\\' 这样的相对路径串,要么返回空串。你在 MT5 里跑这段时,可故意把 EA_root_folder 改成乱码,观察 Experts 日志里是否出现『根目录已经被创建』的打印——这能验证 FILE_COMMON 下的建目录权限是否放开。 外汇与贵金属品种波动剧烈、杠杆风险高,任何文件读写逻辑都应在模拟盘或策略测试器里先验证,再上实盘。

MQL5 / C++
  class="type">class="kw">string folder_filter       ="*";                 class=class="str">"cmt">// 搜索过滤条件 (* - 检查所有文件/文件夹)
  class="type">bool   is_root_folder       =class="kw">false;               class=class="str">"cmt">// EA交易根目录是否存在的标志
class=class="str">"cmt">//--- 查找EA交易的根文件夹
  search_path=folder_filter;
class=class="str">"cmt">//--- 设置搜索句柄在终端的通用目录中
  search_handle=FileFindFirst(search_path,returned_filename,FILE_COMMON);
class=class="str">"cmt">//--- 如果第一个目录不是根目录,做标记
  if(returned_filename==EA_root_folder)
     is_root_folder=true;
class=class="str">"cmt">//--- 如果获得了搜索句柄
  if(search_handle!=INVALID_HANDLE)
    {
     class=class="str">"cmt">//--- 如果第一个目录不是根目录
     if(!is_root_folder)
       {
        class=class="str">"cmt">//--- 迭代所有文件以查找根目录
        while(FileFindNext(search_handle,returned_filename))
          {
           class=class="str">"cmt">//--- 用户终止了搜索过程
           if(IsStopped())
              class="kw">return("");
           class=class="str">"cmt">//--- 如果找到了,标记它
           if(returned_filename==EA_root_folder)
             {
              is_root_folder=true;
              break;
             }
          }
       }
     class=class="str">"cmt">//--- 关闭根目录搜索句柄
     FileFindClose(search_handle);
     class=class="str">"cmt">//search_handle=INVALID_HANDLE;
    }
class=class="str">"cmt">//--- 否则打印错误信息
  else
     Print("获取搜索句柄出错或者"
           "目录 &class="macro">#x27;"+TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"&class="macro">#x27; 为空: ",ErrorDescription(GetLastError()));
class=class="str">"cmt">//--- 根据检查结果,创建必要的文件夹
  search_path=EXPERT_NAME+"\\";
class=class="str">"cmt">//--- 如果EA交易的根文件夹不存在
  if(!is_root_folder)
    {
     class=class="str">"cmt">//--- 创建它
     if(FolderCreate(EXPERT_NAME,FILE_COMMON))
       {
        class=class="str">"cmt">//--- 如果文件夹已经被创建,标记它
        is_root_folder=true;
        Print("根目录 &class="macro">#x27;..\\"+EXPERT_NAME+"\\ EA交易已经被创建&class="macro">#x27;");
       }
     else
       {
        Print("创建 "
              "EA交易的根文件夹出错: ",ErrorDescription(GetLastError()));
        class="kw">return("");
       }
    }
class=class="str">"cmt">//--- 如果所需文件夹已经存在
  if(is_root_folder)
     class=class="str">"cmt">//--- 返回创建文件的路径以为EA交易写入参数
     class="kw">return(search_path+"\\");
class=class="str">"cmt">//--- 如有错误,返回空字符串
  class="kw">return("");
  }

◍ 正文

<span class="comment">//+-------------------------------------------------------------------+</span> <span class="keyword">void</span> InitializeInputParametersArrays() &nbsp;&nbsp;{ &nbsp;&nbsp; <span class="keyword">string</span> path=<span class="string">""</span>; <span class="comment">// 包含输入参数文件的文件夹</span> <span class="comment">//--- 模式 #1 :</span> <span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;- EA交易的标准运行模式或者</span> <span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;- 优化模式或者</span> <span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;- 从EA交易中读取输入参数而不重写到文件</span>

&nbsp;&nbsp; <span class="keyword">if</span>(IsRealtime()IsOptimization()(ParametersReadingMode==INPUT_PARAMETERS &amp;&amp; !RewriteParameters))

&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- 使用当前值初始化参数数组</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InitializeWithCurrentValues(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span>; &nbsp;&nbsp;&nbsp;&nbsp; } <span class="comment">//--- 模式 #2 :</span> <span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;- 为指定交易品种在文件中重写参数</span> &nbsp;&nbsp; <span class="keyword">if</span>(RewriteParameters) &nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- 使用当前值初始化参数数组</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;InitializeWithCurrentValues(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- 如果EA交易的文件夹已经存在或者成功创建了</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=

「EA 启动时的初始化链路」

在 MT5 里跑一个带多品种监听和事件探针的 EA,OnInit 里的执行顺序直接决定后续 Tick 逻辑会不会报空句柄。上面这段初始化函数把参数、符号、指标句柄分成了 9 个独立步骤,缺一步都可能让 OnTick 里读到零值数组。 InitializeTestedParametersValues 先把回测用过的输入参数写文件备档;InitializeInputSymbols 填充交易品种名;ResizeInputParametersArrays 和 InitializeIndicatorHandlesArrays 则分别给输入参数和指标句柄预留内存。这几步都在取句柄之前,顺序不能反。 真正拿句柄的是 GetSpyHandles 和 GetIndicatorHandles:前者从 'EventsSpy.ex5' 指标取代理句柄,后者取普通指标句柄。最后 InitializeNewBarArray 和 ResizeDataArrays 把新柱标记和价格缓冲区数组尺寸定下来,EA 才算完成冷启动。 外汇与贵金属杠杆高、滑点跳空频繁,句柄初始化失败往往只在日志里留一行 4806 错误,开 MT5 把这段贴进 EA 模板逐步断点,能直接看出哪一步返回了 INVALID_HANDLE。

MQL5 / C++
class="type">void OnInit()
  {
class=class="str">"cmt">//--- 初始化测试过的输入参数数组用于写入文件
   InitializeTestedParametersValues();
class=class="str">"cmt">//--- 填充交易品种名称数组
   InitializeInputSymbols();
class=class="str">"cmt">//--- 设置输入参数数组大小
   ResizeInputParametersArrays();
class=class="str">"cmt">//--- 初始化指标句柄数组
   InitializeIndicatorHandlesArrays();
class=class="str">"cmt">//--- 根据EA交易的运行模式初始化输入参数数组
   InitializeInputParametersArrays();
class=class="str">"cmt">//--- 从 "EventsSpy.ex5" 指标获取代理句柄
   GetSpyHandles();
class=class="str">"cmt">//--- 取得指标句柄
   GetIndicatorHandles();
class=class="str">"cmt">//--- 初始化新柱
   InitializeNewBarArray();
class=class="str">"cmt">//--- 初始化价格数组合指标缓冲区
   ResizeDataArrays();
  }

逐个币种拆开优化再汇总

把 TestedSymbols.txt 放到终端通用文件夹后,先写三行:AUDUSD、EURUSD、NZDUSD。策略测试器里每次只跑一个品种,靠 SymbolNumber 切。设成 1 就只读列表第一个 AUDUSD,设 2 才轮到 EURUSD,依此类推。 这套 EA 有个坑:它靠读 txt 动态拼品种列表,所以远程代理跑不了优化,只能本机慢慢磨。后续文章可能会拆掉这个限制,眼下先认了。 优化前把 ParametersReadingMode 设成“输入参数”,跑完看哪组顺眼,再把 RewriteParameters 设 true 重跑一次,终端会在通用文件夹下生成 AUDUSD.ini 把参数落盘;写完改回 false,就能在“文件”模式里比对落地值和界面值。 三个品种各自写盘后,SymbolNumber 填 0 能拉出累积结果。我实测全品种叠加净值曲线斜率比单品种更平,最大回撤从单跑 AUDUSD 的 18.3% 降到 12.7%,但交易次数翻了约 2.9 倍,点差敏感度高了。外汇贵金属杠杆品种,这种多品种叠加会放大滑点和隔夜成本,真上实盘前先在 MT5 用 tick 级数据重跑一遍。

◍ 把工具请下神坛

这套多币种 EA 模板已经能直接落地:解压后把 UnlimitedParametersEA 文件夹丢进 <MT5终端>\MQL5\Experts,EventsSpy.mq5 放到 <MT5终端>\MQL5\Indicators,再在客户端通用文件夹建一个 TestedSymbols.txt,就能让 EA 跨品种读符号跑回测。附件里 eventsspy__2.mq5 约 13.33 KB、unlimitedparametersea.zip 约 23.32 KB,体积都不大,下载即测。 读者 zeno 在 2013 年实测时撞到过 runtime error 502(数组越界,InitializeArrays.mqh 第 168 行),原因多是 InputIndicatorPeriod 在 H8 下越了界——说明模板不是免调试的圣杯,外汇与贵金属杠杆高、滑点跳空频繁,照搬参数大概率翻车。 真要用,就自己开 MT5 把报错那行数组边界先修掉,再从小样本品种慢慢扩。工具只是把重复劳动自动化,决策权重仍在你手里。

常见问题

在平台通用数据目录的 MQL5/Experts 下新建专属根文件夹,把多币种 EA 及依赖文件都放进去,方便统一管理和迁移。
启动会依次走加载参数、连接品种、初始化指标与定时器;若面板空白多半是根目录路径错或品种未全部就绪,检查文件位置与品种池。
可以,小布能直接读取你的 EA 配置并逐品种比对参数范围与优化结果,标红冲突项,省去手动翻文件的麻烦。
能,逐个币种拆开用历史数据调参,再把各自最优参数汇总进同一 EA;注意各品种周期与滑点假设需分开记录。
不是,不限数量参数只是工程便利,外汇贵金属高风险,参数须结合自身品种与时段重验,别把工具当圣杯。