MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易·综合运用
在通用目录里定位并自建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 下的建目录权限是否放开。 外汇与贵金属品种波动剧烈、杠杆风险高,任何文件读写逻辑都应在模拟盘或策略测试器里先验证,再上实盘。
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() { <span class="keyword">string</span> path=<span class="string">""</span>; <span class="comment">// 包含输入参数文件的文件夹</span> <span class="comment">//--- 模式 #1 :</span> <span class="comment">// - EA交易的标准运行模式或者</span> <span class="comment">// - 优化模式或者</span> <span class="comment">// - 从EA交易中读取输入参数而不重写到文件</span>
| <span class="keyword">if</span>(IsRealtime() | IsOptimization() | (ParametersReadingMode==INPUT_PARAMETERS && !RewriteParameters)) |
|---|
{ <span class="comment">//--- 使用当前值初始化参数数组</span> InitializeWithCurrentValues(); <span class="keyword">return</span>; } <span class="comment">//--- 模式 #2 :</span> <span class="comment">// - 为指定交易品种在文件中重写参数</span> <span class="keyword">if</span>(RewriteParameters) { <span class="comment">//--- 使用当前值初始化参数数组</span> InitializeWithCurrentValues(); <span class="comment">//--- 如果EA交易的文件夹已经存在或者成功创建了</span> <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。
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 把报错那行数组边界先修掉,再从小样本品种慢慢扩。工具只是把重复劳动自动化,决策权重仍在你手里。