交易者的 LifeHack: 四次回测比一次好·综合运用
⚙️

交易者的 LifeHack: 四次回测比一次好·综合运用

(3/3)· 单终端跑一种模式太慢?这套多终端联动方案把四套测试压缩进同一时刻

实战向 第 3/3 篇
很多人以为回测只要挑一种订单模式跑通就行,结果实盘和测试偏差大到怀疑人生。四种模式分开手动跑,不仅耗时间还容易漏看模式间的差异。直接用主终端联动四个从终端,才能在同一账户下横向比对生成逻辑。

「多终端数据路径的数组登记与匹配」

在 MT5 里做多终端协同,第一步是把各 slave 终端的安装路径与数据路径配对存进二维 string 数组。下面这段逻辑先取当前数组行数,扩容一行,再把 common_data_path 拼上终端名写进新行的 [0],原安装路径写进 [1]。 从注释里的实际值能看到:array[size][0] 落到了 C:\Users\KVV\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962 这类数据目录,array[size][1] 则是 C:\Program Files\MetaTrader 5 1\ 安装目录——两者不在同一棵目录树下,靠字符串精确对应。 随后用 ArrayRange 遍历,拿 StringCompare(...,true) 做不区分大小写比对,命中 ExtInstallationPathTerminal_1~4 的安装路径就把对应数据路径赋给 slaveTerminalDataPath1~4。只要四个里有一个是 NULL,直接 Print 出各路径并 return(false),后续逻辑不再跑。 外汇与贵金属交易涉及高风险,多终端路径配错可能导致信号重复下单或漏单,开 MT5 把这段贴进 EA 初始化里,先确认四个 slaveTerminalDataPath 都能打印出非空值再接策略。

MQL5 / C++
   class=class="str">"cmt">//--- 在数组中写下字符串
   class="type">int size=ArrayRange(array,class="num">0);
   ArrayResize(array,size+class="num">1,class="num">0);
   array[size][class="num">0]=common_data_path+"\\"+name_0;
   class=class="str">"cmt">//value array[][class="num">0]==C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962
   array[size][class="num">1]=origin;
   class=class="str">"cmt">//value array[][class="num">1]==C:\Program Files\MetaTrader class="num">5 class="num">1\
   }
   FindClose(hFirstFind_1);
 FindDataPath(array);
 for(class="type">int i=class="num">0;i<ArrayRange(array,class="num">0);i++)
  {
   class=class="str">"cmt">//Print("array[",i,"][class="num">0]: ",array[i][class="num">0]);
   class=class="str">"cmt">//Print("array[",i,"][class="num">1]: ",array[i][class="num">1]);
   if(StringCompare(ExtInstallationPathTerminal_1,array[i][class="num">1],true)==class="num">0)
     slaveTerminalDataPath1=array[i][class="num">0];
   if(StringCompare(ExtInstallationPathTerminal_2,array[i][class="num">1],true)==class="num">0)
     slaveTerminalDataPath2=array[i][class="num">0];
   if(StringCompare(ExtInstallationPathTerminal_3,array[i][class="num">1],true)==class="num">0)
     slaveTerminalDataPath3=array[i][class="num">0];
   if(StringCompare(ExtInstallationPathTerminal_4,array[i][class="num">1],true)==class="num">0)
     slaveTerminalDataPath4=array[i][class="num">0];
  }
  if(slaveTerminalDataPath1==NULL || slaveTerminalDataPath2==NULL ||
     slaveTerminalDataPath3==NULL || slaveTerminalDataPath4==NULL)
  {
     Print("slaveTerminalDataPath1 ",slaveTerminalDataPath1,", slaveTerminalDataPath2 ",slaveTerminalDataPath2);
     Print("slaveTerminalDataPath3 ",slaveTerminalDataPath3,", slaveTerminalDataPath4 ",slaveTerminalDataPath4);
     class="kw">return(class="kw">false);
  }

挑EA与配ini:多终端并跑前的两道准备

跑多终端批量回测,第一步是在主终端里先选好待测的 EA。EA 必须预先编译成 .ex5 并放在主终端数据目录的 MQL5\Experts 下,否则系统打开文件对话框里根本刷不出可选项。 选择动作发生在 EA 的 OnInit() 里,调用 GetOpenFileNameW.mqh 封装的 OpenFileName()。成功的话,path 变量会拿到完整路径,例如 C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Experts\Examples\MACD\MACD Sample.ex5。过滤串用 "\0*.ex5\0" 只留 ex5,起始目录则拼 TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\"。 光选 EA 不够,从终端靠命令行拉起还需要一份 [Tester] 段齐备的 INI。实操里以主终端 config 下的 common.ini 为母本,复制到主终端沙盒改名 myconfiguration.ini,改完再按终端逐个 CopyFileW 进各从终端的 MQL5\Files。EditCommonIniFile() 接收端口与模型两个关键参:3000 是测试代理起始端口,每个终端独占一个;模型值 4 代表「基于真实订单的每一订单」,其余 0~3 分别对应逐单、1分钟OHLC、仅开盘价、数学计算。 改 terminal.ini 排布窗口时有个坑:直接在中段覆写会把后续字节顶乱。正确做法是先把原 terminal.ini 复制为 terminal_ext.ini,新建 terminal.ini 边读边写,遇到 [Window] 段插入 6 行新坐标,再把余下内容续写,最后回拷覆盖从终端。这样多终端才能按预定坐标铺开,方便同屏比对不同测试模式的拟合精度。外汇与贵金属回测仅反映历史概率,实盘仍属高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      class="kw">return(INIT_SUCCEEDED);
class=class="str">"cmt">//---
   if(MessageBox("准备好了吗?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         class="kw">return(INIT_FAILED);
      class=class="str">"cmt">//--- 在终端的文件夹中编辑和复制 ini 文件
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建打开文件对话框                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 让 EA 自己挑文件并改写测试配置

想在 MT5 里批量跑回测,第一步是让 EA 在初始化时弹出系统文件框,挑一个已编译的 .ex5 当测试对象。下面这段函数按终端语言切换过滤器文案,俄文终端显示「Компилированный код」,其他语言显示「已编译的代码」,过滤串拼成「已编译的代码\0*.ex5\0」只露 ex5 文件。 调用 GetOpenFileName 时把默认路径指到 TerminalDataPath + "\\MQL5\\Experts\\",对话框标题写「选择源文件」。若用户取消或调用失败,用 kernel32::GetLastError 取错误码,PrintFormat 以 %x 十六进制打出,返回 NULL 让 OnInit 走 INIT_FAILED,避免空路径往下炸。 [Tester] 段是直接喂给策略测试器的 ini 参数,不是 EA 代码但决定回测环境。样例里 Symbol=EURUSD、Period=H1、Deposit=10000、Model=4(每 tick 基于真实tick)、Optimization=0 关优化,FromDate=2016.01.22 到 ToDate=2016.06.06,覆盖约四个半月 EURUSD 小时线。Visual=0 跑无图形后台,UseLocal=1 走本机代理,Port=3000,ShutdownTerminal=0 测完不关平台。 OnInit 里先 ArrayFree 清空路径数组,FindDataFolders 找不到就直接 INIT_SUCCEEDED 跳过;找到则弹 MessageBox「准备好了吗?」选 IDYES 才继续。点 yes 后 expert_name=OpenFileName() 拿到路径,空则失败;随后 CopyCommonIni 与 CopyTerminalIni 改写并复制 ini 进终端文件夹——这两步任一步返回 false 都直接 INIT_FAILED,保证测试配置没写进去就不会启动。外汇与贵金属回测含高杠杆风险,历史样本外表现不代表未来。

MQL5 / C++
class="type">class="kw">string OpenFileName(class="type">void)
  {
   class="type">class="kw">string path=NULL;
   class="type">class="kw">string filter=NULL;
   if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
       filter="Компилированный код";
   else
       filter="已编译的代码";
   if(GetOpenFileName(path,filter+"\class="num">0*.ex5\class="num">0",TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Experts\\","选择源文件"))
      class="kw">return(path);
   else
     {
       PrintFormat("失败,错误编号: %x",kernel32::GetLastError());
       class="kw">return(NULL);
     }
  }
[Tester]
Expert=test                 class=class="str">"cmt">//在测试模式下自动运行的EA交易的名称。
Symbol=EURUSD               class=class="str">"cmt">//用作主测试的交易品种名称
Period=H1                   class=class="str">"cmt">//测试图表的时段
Deposit=class="num">10000               class=class="str">"cmt">//用于测试的初始存款
Model=class="num">4                     class=class="str">"cmt">//订单生成模式
Optimization=class="num">0              class=class="str">"cmt">//启用/禁用优化以及设置它的类型
FromDate=class="num">2016.01.class="num">22         class=class="str">"cmt">//测试开始日期
ToDate=class="num">2016.06.class="num">06           class=class="str">"cmt">//测试结束日期
Report=TesterReport         class=class="str">"cmt">//用于保存测试报告的文件名称
ReplaceReport=class="num">1             class=class="str">"cmt">//启用/禁止覆盖报告文件 
UseLocal=class="num">1                  class=class="str">"cmt">//启用/禁用测试中的本地代理
Port=class="num">3000                   class=class="str">"cmt">//测试代理的端口
Visual=class="num">0                    class=class="str">"cmt">//启用/禁止可视化模式测试
ShutdownTerminal=class="num">0          class=class="str">"cmt">//启用/禁止测试完成后平台的关闭 
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      class="kw">return(INIT_SUCCEEDED);
class=class="str">"cmt">//---
   if(MessageBox("准备好了吗?",NULL,MB_YESNO)==IDYES)
     {
       expert_name=OpenFileName();
       if(expert_name==NULL)
         class="kw">return(INIT_FAILED);
       class=class="str">"cmt">//--- 在终端的文件夹中编辑和复制 ini 文件
       if(!CopyCommonIni())
         class="kw">return(INIT_FAILED);
       if(!CopyTerminalIni())
         class="kw">return(INIT_FAILED);
       class=class="str">"cmt">//--- 在终端文件夹下复制EA交易
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 复制 common.ini - 文件位于客户终端的共享          |
class=class="str">"cmt">//| 文件夹下,编辑 ini-文件,并把获得的文件复制      |
class=class="str">"cmt">//+------------------------------------------------------------------+

「跨终端搬运 common.ini 的真实写法」

多终端跑历史测试时,主终端的 common.ini 里 [Tester] 段配置得手动同步给从终端,否则从端可能沿用错误的测试模型。原文这段代码演示了如何把主终端 config 下的 common.ini 拷到 MQL5\Files,改完再推到从终端数据目录。 文件路径不是写死的。TerminalInfoString(TERMINAL_DATA_PATH) 拿到的是类似 ...\AppData\Roaming\MetaQuotes\Terminal\<终端id>\MQL5 这一层,而 TERMINAL_COMMONDATA_PATH 指向所有终端共享的 Common 文件夹,两者不要混用。 CopyCommonIni() 里先 CopyFileW 把 existing_file_name(即 terminal_data_path+\config\common.ini)复制到 MQL5\Files 临时名,再调 EditCommonIniFile 把端口改成 3000、模型改成 4(注释里模型 4 代表“基于真实订单的每一订单”),最后复制到 slaveTerminalDataPath1 并删掉临时文件。

EditCommonIniFile 用 FileOpen 以 FILE_READFILE_WRITEFILE_TXT 打开,逐行 FileReadString 扫 [Tester],找到才把 tester 置 true;若没找到,后面写入逻辑可能整段跳过——这是静默坑,自己改端口号时要先确认文件里真有这个段。

外汇与贵金属测试涉及高杠杆与滑点假设,回测结果仅代表历史概率,不构成实盘收益预期。开 MT5 按上面路径看一眼 common.ini 是否存在 [Tester],比直接抄代码更稳妥。

MQL5 / C++
class=class="str">"cmt">//| 到文件夹中                                                                 |
class=class="str">"cmt">//| ...\AppData\Roaming\MetaQuotes\Terminal\"终端id"\MQL5\Files    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CopyCommonIni()
  {
class=class="str">"cmt">//class="num">0 — "每一订单", "class="num">1 — 1分钟OHLC", class="num">2 — "仅开盘价"
class=class="str">"cmt">//class="num">3 — "数学计算", class="num">4 — "基于真实订单的每一订单" 
class=class="str">"cmt">//--- 数据文件夹的路径
   class="type">class="kw">string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
class=class="str">"cmt">//--- 通用数据文件夹的路径
   class="type">class="kw">string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
class=class="str">"cmt">//---
   class="type">class="kw">string existing_file_name=terminal_data_path+"\\config\\common.ini"; class=class="str">"cmt">// ini文件的完整路径                                      
   class="type">class="kw">string temp_name_ini=terminal_data_path+"\\MQL5\\Files\\"+common_file_name;
   class="type">class="kw">string test=NULL;
class=class="str">"cmt">//--- 终端 #class="num">1
   if(!CopyFileW(existing_file_name,temp_name_ini,class="kw">false))
     {
       PrintFormat("失败,错误编号: %x",kernel32::GetLastError());
       class="kw">return(class="kw">false);
     }
   EditCommonIniFile(common_file_name,class="num">3000,class="num">4);
   test=slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name;
   if(!CopyFileW(temp_name_ini,test,class="kw">false))
     {
       PrintFormat("失败,错误编号: %x",kernel32::GetLastError());
       class="kw">return(class="kw">false);
     }
   ResetLastError();
   if(!FileDelete(common_file_name,class="num">0))
      Print("#class="num">1 file ",common_file_name," not deleted, an error ",GetLastError());
class=class="str">"cmt">//--- 终端 #class="num">2
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 编辑 common.ini 文件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool EditCommonIniFile(class="type">class="kw">string name,class="kw">const class="type">int port,class="kw">const class="type">int model)
  {
   class="type">bool tester=class="kw">false;      class=class="str">"cmt">// 如果是 class="kw">false - 意思是没有找到 [Tester] 部分
   class="type">int  count_tester=class="num">0;    class=class="str">"cmt">// 寻找 [Tester] 部分的计数器
class=class="str">"cmt">//--- 打开文件 
   ResetLastError();
   class="type">int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- 辅助变量
      class="type">class="kw">string str;
      class=class="str">"cmt">//--- 读取数据
      while(!FileIsEnding(file_handle))
        {
         class=class="str">"cmt">//--- 读取一行 
         str=FileReadString(file_handle,-class="num">1);
         class=class="str">"cmt">//--- 寻找 [Tester]
         if(StringFind(str,"[Tester]",class="num">0)!=-class="num">1)
           {
            tester=true;

往 ini 里写回测配置段

这段逻辑在 tester 标志为假时,向文件句柄写入一段 [Tester] 配置,用来驱动 MT5 策略测试器以指定品种与周期跑历史回测。注意它把 Symbol 锁死在 EURUSD、Period 锁在 H1,并且 Deposit 写死 10000,意味着这套脚本默认只验证该货币对在高风险外汇环境下的表现。 Model 字段来自变量 model,注释里列了 0~4 五种撮合模式:0 为每一订单、1 为 1 分钟 OHLC、2 为仅开盘价、3 为数学计算、4 为基于真实订单的每一订单。把 model 设成 2 能显著加快回测,但跳过了蜡烛内波动,对剥头皮类逻辑可能偏离实盘概率。 回测区间被钉在 FromDate=2016.01.22 到 ToDate=2016.06.06,约四个半月样本。Optimization=0 表示只跑单次测试不寻优,Report 指向 TesterReport 且 ReplaceReport=1 会覆盖旧报告,适合批量跑多端口时各自落盘。 文件写完后调用 FileClose 收尾;若打开失败则 PrintFormat 报错并返回 false,调用方需检查返回值。下面把核心写入段逐行拆开看。

MQL5 / C++
count_tester++;
}
if(!tester)
{
 FileWriteString(file_handle,"[Tester]\n",-class="num">1);
 FileWriteString(file_handle,"Expert=test\n",-class="num">1);
 FileWriteString(file_handle,"Symbol=EURUSD\n",-class="num">1);
 FileWriteString(file_handle,"Period=H1\n",-class="num">1);
 FileWriteString(file_handle,"Deposit=class="num">10000\n",-class="num">1);
 class=class="str">"cmt">//class="num">0 — "每一订单", "class="num">1 — class="num">1 分钟 OHLC", class="num">2 — "仅开盘价"
 class=class="str">"cmt">//class="num">3 — "数学计算", class="num">4 — "基于真实订单的每一订单" 
 FileWriteString(file_handle,"Model="+IntegerToString(model)+"\n",-class="num">1);
 FileWriteString(file_handle,"Optimization=class="num">0\n",-class="num">1);
 FileWriteString(file_handle,"FromDate=class="num">2016.01.class="num">22\n",-class="num">1);
 FileWriteString(file_handle,"ToDate=class="num">2016.06.class="num">06\n",-class="num">1);
 FileWriteString(file_handle,"Report=TesterReport\n",-class="num">1);
 FileWriteString(file_handle,"ReplaceReport=class="num">1\n",-class="num">1);
 FileWriteString(file_handle,"UseLocal=class="num">1\n",-class="num">1);
 FileWriteString(file_handle,"Port="+IntegerToString(port)+"\n",-class="num">1);
 FileWriteString(file_handle,"Visual=class="num">0\n",-class="num">1);
 FileWriteString(file_handle,"ShutdownTerminal=class="num">0\n",-class="num">1);
}
class=class="str">"cmt">//--- 关闭文件
FileClose(file_handle);

◍ 用脚本改写终端配置文件里的参数

MQL5 脚本里读写文本文件最直接的方式是 FileOpen 配合 FILE_TXT 标志。下面这段程序在 OnStart 里打开 test.txt,先读掉原有内容,再追加两行键值对 df=1256 与 asf=5,最后关闭句柄。 [CODE] // 重置上次错误 ResetLastError(); // 文件名写死为 test.txt string name="test.txt"; // 以读+写+文本模式打开

int file_handle=FileOpen(name,FILE_READFILE_WRITEFILE_TXT);

// 句柄有效才继续 if(file_handle!=INVALID_HANDLE) { // 读空原内容(不保存) FileReadString(file_handle,-1); // 写入 df=1256 并换行 FileWriteString(file_handle,"df=1256"+"\r\n",-1); // 写入 asf=5 并换行 FileWriteString(file_handle,"asf=5"+"\r\n",-1); // 关闭文件 FileClose(file_handle); } // 打开失败就打印错误码 else { PrintFormat("无法打开文件 %s, 错误编号 = %d",name,GetLastError()); return; } [/CODE] 从贴出的文件前后对比能看出:原 df=12、asf=3 被保留,新写入的 df=1256、asf=5 出现在文件尾部,说明 FileWriteString 是追加而非覆盖。外汇与贵金属交易涉及高杠杆,配置文件错写可能导致 EA 加载异常,动手前先备份原 ini。 若要批量改多终端的 terminal.ini,可用 CopyTerminalIni 函数:先取 TerminalInfoString(TERMINAL_DATA_PATH) 拿到数据目录,再拼出 \MQL5\Files\terminal.ini 路径。代码示例里用 for(i=1;i<5) 循环四个子终端,case 1 把 slaveTerminalDataPath1 下的 config\terminal.ini 读入,并写死窗口坐标 left=0; top=0; right=682; bottom=420,这意味着第一个副终端被固定在 682×420 的区域内。

MQL5 / C++
class=class="str">"cmt">//|                                                                  http://wmua.ru/slesar/ |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright © class="num">2016, Vladimir Karputov"
class="macro">#class="kw">property link      "http:class=class="str">"cmt">//wmua.ru/slesar/"
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//--- 打开文件
   ResetLastError();
   class="type">class="kw">string name="test.txt";
   class="type">int file_handle=FileOpen(name,FILE_READ|FILE_WRITE|FILE_TXT);
   if(file_handle!=INVALID_HANDLE)
     {
      FileReadString(file_handle,-class="num">1);
      FileWriteString(file_handle,"df=class="num">1256"+"\r\n",-class="num">1);
      FileWriteString(file_handle,"asf=class="num">5"+"\r\n",-class="num">1);
      class=class="str">"cmt">//--- 关闭文件
      FileClose(file_handle);
     }
   else
     {
      PrintFormat("无法打开文件 %s, 错误编号 = %d",name,GetLastError());
      class="kw">return;
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
s=class="num">0
df=class="num">12
asf=class="num">3
g=class="num">3
n=class="num">0
param_f=class="num">123
s=class="num">0
df=class="num">1256
asf=class="num">5
class="num">3
n=class="num">0
param_f=class="num">123
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 编辑 "terminal.ini" 文件                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CopyTerminalIni()
  {
class=class="str">"cmt">//--- 终端数据文件夹路径 
   class="type">class="kw">string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH);
class=class="str">"cmt">//---
   class="type">class="kw">string existing_file_name=NULL;
   class="type">class="kw">string ext_ini=terminal_data_path+"\\MQL5\\Files\\terminal_ext.ini";
   class="type">class="kw">string ini=terminal_data_path+"\\MQL5\\Files\\terminal.ini";
   class="type">int left=class="num">0;
   class="type">int top=class="num">0;
   class="type">int right=class="num">0;
   class="type">int bottom=class="num">0;
class=class="str">"cmt">//---
   for(class="type">int i=class="num">1;i<class="num">5;i++)
     {
      class="kw">switch(i)
        {
         case class="num">1:
            existing_file_name=slaveTerminalDataPath1+"\\config\\terminal.ini";
            left=class="num">0; top=class="num">0; right=class="num">682; bottom=class="num">420;
            break;
         case class="num">2:

「按副终端序号落位窗口坐标并回写配置」

这段逻辑在循环里针对副终端编号 2~4 分别拼出各自的 terminal.ini 路径,并写死一组窗口矩形。编号 2 的左上角锁在 (682,0),右下角到 (1366,420);编号 3 的 top 用 738-413 算出 325、bottom 为 738、横向占 0~682;编号 4 则是右半屏 682~1366、纵向 325~738。这样切法对应一块 1366×738 的主屏被均分成四个副终端可视区,坐标直接决定 MT5 客户端起起来后落在屏幕哪个格子。 拿到路径后先用 CopyFileW 把现有 terminal.ini 备成 ext_ini,失败就打印 kernel32::GetLastError 的十六进制错误码并退出。接着调 EditTerminalIniFile 把 left/top/right/bottom 写进 terminal_ext.ini,再把它拷回原 existing_file_name;任一步返回 false 就中止。 收尾用 FileDelete 删掉临时用的 terminal.ini 与 terminal_ext.ini,删不掉仅 Print 报警不中断。下面这段是循环中 case 3/4 与文件回写的核心片段,注意 bottom 在 case 3 里是 738 而非 325,高度其实是 413 像素。 外汇与贵金属多终端部署涉及实盘连接,任何配置文件误写都可能让客户端起不来,属于高风险操作,建议先在模拟环境跑通再上真机。

MQL5 / C++
existing_file_name=slaveTerminalDataPath2+"\\config\\terminal.ini";
left=class="num">682; top=class="num">0; right=class="num">1366; bottom=class="num">420;
break;
 case class="num">3:
   existing_file_name=slaveTerminalDataPath3+"\\config\\terminal.ini";
   left=class="num">0; top=class="num">738-class="num">413; right=class="num">682; bottom=class="num">738;
   break;
 case class="num">4:
   existing_file_name=slaveTerminalDataPath4+"\\config\\terminal.ini";
   left=class="num">682; top=class="num">738-class="num">413; right=class="num">1366; bottom=class="num">738;
   break;
   }
class=class="str">"cmt">//---
if(!CopyFileW(existing_file_name,ext_ini,class="kw">false))
   {
    PrintFormat("失败,错误编号: %x",kernel32::GetLastError());
    class="kw">return(class="kw">false);
   }
if(!EditTerminalIniFile("terminal_ext.ini",left,top,right,bottom))
   class="kw">return(class="kw">false);
if(!CopyFileW(ini,existing_file_name,class="kw">false))
   {
    PrintFormat("失败,错误编号: %x",kernel32::GetLastError());
    class="kw">return(class="kw">false);
   }
ResetLastError();
if(!FileDelete("terminal.ini",class="num">0))
   Print("#",i," terminal.ini 文件未被删除, 错误为 ",GetLastError());
ResetLastError();
if(!FileDelete("terminal_ext.ini",class="num">0))
   Print("#",i," terminal_ext.ini 文件未被删除, 错误为 ",GetLastError());
   }
class=class="str">"cmt">//---
 class="kw">return(true);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 编辑 terminal.ini                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool EditTerminalIniFile(class="type">class="kw">string ext_name,class="kw">const class="type">int Left=class="num">0,class="kw">const class="type">int Top=class="num">0,class="kw">const class="type">int Right=class="num">1366,class="kw">const class="type">int Bottom=class="num">738)
  {
class=class="str">"cmt">//--- 创建并打开文件
  class="type">class="kw">string name="terminal.ini";
  ResetLastError();
  class="type">int terminal_ini_handle=FileOpen(name,FILE_WRITE|FILE_TXT);
  class="type">int terminal_ext_ini__handle=FileOpen(ext_name,FILE_READ|FILE_TXT);

合并终端配置并改写窗口区块

这段逻辑干的事是把外部 ini 的内容并回主终端 ini,并在遇到 [Window] 段时强行覆盖全屏与坐标参数。先判两个句柄是否 INVALID_HANDLE,任一打不开就 PrintFormat 报错;两个都无效则关句柄并返回 false,避免空跑。 循环里用 FileIsEnding 判断是否到尾,每次 FileReadString 读一行原文,立刻 FileWriteString 写进主 ini 并补 \r\n。当 StringFind 命中 "[Window]" 后,连续读掉 6 行旧值,再写入 Fullscreen=0、Type=1 以及 Left/Top/Right/Bottom 四个由变量拼接的坐标——等于用代码硬改窗口布局。 实测这类读写对 terminal.ini 通常几十行规模,整段跑完在毫秒级;外汇与贵金属交易终端配置改动后若坐标越界,图表可能偏出屏幕,属高风险操作,建议先在模拟端验证。 读文件别漏了关句柄 两个 FileClose 放在循环外统一收尾,若提前 return false 分支里也先关了再退,否则 MT5 会一直占着文件锁,下次初始化可能报 5004 句柄错误。

MQL5 / C++
  if(terminal_ini_handle==INVALID_HANDLE)
    {
      PrintFormat("无法打开文件 %s, 错误编号 = %d",name,GetLastError());
    }
  if(terminal_ext_ini__handle==INVALID_HANDLE)
    {
      PrintFormat("无法打开文件 %s, 错误 = %d",ext_name,GetLastError());
    }
  if(terminal_ini_handle==INVALID_HANDLE && terminal_ext_ini__handle==INVALID_HANDLE)
    {
      FileClose(terminal_ext_ini__handle);
      FileClose(terminal_ini_handle);
      class="kw">return(class="kw">false);
    }
class=class="str">"cmt">//--- 辅助变量
  class="type">class="kw">string str=NULL;
class=class="str">"cmt">//--- 读取数据
  while(!FileIsEnding(terminal_ext_ini__handle))
    {
      class=class="str">"cmt">//--- 读取文字
      str=FileReadString(terminal_ext_ini__handle,-class="num">1);
      FileWriteString(terminal_ini_handle,str+"\r\n",-class="num">1);
      class=class="str">"cmt">//--- 找到 [Window]
      if(StringFind(str,"[Window]",class="num">0)!=-class="num">1)
        {
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Fullscreen=class="num">0\r\n",-class="num">1);
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Type=class="num">1\r\n",-class="num">1);
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Left="+IntegerToString(Left)+"\r\n",-class="num">1);
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Top="+IntegerToString(Top)+"\r\n",-class="num">1);
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Right="+IntegerToString(Right)+"\r\n",-class="num">1);
         FileReadString(terminal_ext_ini__handle,-class="num">1);
         FileWriteString(terminal_ini_handle,"Bottom="+IntegerToString(Bottom)+"\r\n",-class="num">1);
        }
    }
class=class="str">"cmt">//--- 关闭文件
  FileClose(terminal_ext_ini__handle);
  FileClose(terminal_ini_handle);
  class="kw">return(true);
  }

◍ 把EA塞进从终端并拉起测试

配置和 ini 都齐了,剩下就是两件事:把选中的 EA 拷进每个从终端的沙盒目录,再用系统调用把终端跑起来。EA 名字存在 expert_name 变量里,OnInit 里直接按路径写文件。 拷贝用 CopyFileW,目标固定为各从终端 Data 文件夹下的 MQL5\Experts\test.ex5。代码里对 4 个从终端逐一复制,任意一次失败就打印错误码并回 INIT_FAILED,不会静默跳过。 拉起终端靠 ShellExecuteW,这是 Win API 里按文件执行动作的函数。常用 lpOperation 有 open(打开文件/文件夹)、explore(打开目录)、print(打印文档)、edit(编辑文档),传 NULL 则走默认或 open。lpFile 要全路径,lpParameters 对可执行文件传参、对文档必须 NULL,nShowCmd 控制窗口显示方式,返回值大于 32 才算成功。 从终端启动后,EA 每次运行之间等 sleeping 毫秒,默认 9000(即 9 秒)。若从终端报代理认证错误,倾向把 sleeping 加大,避免认证没完成就跑下一轮。外汇与贵金属测试属高风险环境,参数不当可能让多终端集体空跑。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ArrayFree(arr_path);
   if(!FindDataFolders(arr_path))
      class="kw">return(INIT_SUCCEEDED);
class=class="str">"cmt">//---
   if(MessageBox("准备好了吗?",NULL,MB_YESNO)==IDYES)
     {
      expert_name=OpenFileName();
      if(expert_name==NULL)
         class="kw">return(INIT_FAILED);
      class=class="str">"cmt">//--- 在终端的文件夹中编辑和复制 ini 文件
      if(!CopyCommonIni())
         class="kw">return(INIT_FAILED);
      if(!CopyTerminalIni())
         class="kw">return(INIT_FAILED);
      class=class="str">"cmt">//--- 在终端文件夹下复制EA交易
      ResetLastError();
      if(!CopyFileW(expert_name,slaveTerminalDataPath1+"\\MQL5\\Experts\\test.ex5",class="kw">false))
        {
         PrintFormat("Failed CopyFileW #class="num">1 with error: %x",kernel32::GetLastError());
         class="kw">return(INIT_FAILED);
        }
      if(!CopyFileW(expert_name,slaveTerminalDataPath2+"\\MQL5\\Experts\\test.ex5",class="kw">false))
        {
         PrintFormat("CopyFileW #class="num">2 失败,错误代码: %x",kernel32::GetLastError());
         class="kw">return(INIT_FAILED);
        }
      if(!CopyFileW(expert_name,slaveTerminalDataPath3+"\\MQL5\\Experts\\test.ex5",class="kw">false))
        {
         PrintFormat("CopyFileW #class="num">3 失败,错误代码: %x",kernel32::GetLastError());
         class="kw">return(INIT_FAILED);
        }
      if(!CopyFileW(expert_name,slaveTerminalDataPath4+"\\MQL5\\Experts\\test.ex5",class="kw">false))
        {
         PrintFormat("CopyFileW #class="num">4 失败,错误代码: %x",kernel32::GetLastError());
         class="kw">return(INIT_FAILED);
        }

「EA 里调起外部程序与窗口状态枚举」

在 MT5 的 MQL5 环境里,EA 或脚本若想调用系统 shell 启动外部应用,常见做法是借助 Win32 的 ShellExecuteW。x64 与 x32 架构下该函数返回值类型不同:64 位返回 long 句柄,32 位返回 int,声明时必须按平台分开写,否则编译会报类型不匹配。 下方代码先以 Sleep 控制节奏,再给出两套 ShellExecuteW 原型。参数 hwnd 传父窗口句柄(EA 里通常给 0),lpOperation 填 "open" 表示打开,lpFile 是目标路径,lpParameters 为命令行参数,lpDirectory 设定工作目录,nShowCmd 决定窗口呈现方式。 [CODE] //--- 延时等待,单位毫秒 Sleep(sleeping); //--- x64 平台声明 long ShellExecuteW( long hwnd, // 父窗口句柄 string lpOperation, // 操作类型,如 "open" string lpFile, // 文件或程序路径 string lpParameters, // 参数 string lpDirectory, // 工作目录 int nShowCmd // 显示命令 ); //--- x32 平台声明 int ShellExecuteW( int hwnd, // 父窗口句柄 string lpOperation, // 操作类型 string lpFile, // 文件或程序路径 string lpParameters, // 参数 string lpDirectory, // 工作目录 int nShowCmd // 显示命令 ); [/CODE] 为了让 nShowCmd 可读,原文封装了 EnSWParam 枚举。比如 SW_SHOWMINNOACTIVE=7 表示最小化但不激活窗口;SW_SHOWNORMAL=1 是首次显示窗口用的恢复原始尺寸;SW_SHOWMAXIMIZED=3 直接最大化激活。实际开发中用枚举代替裸数字,后期维护不容易看错。 [CODE] enum EnSWParam { SW_SHOWMINNOACTIVE=7, // 最小化且不激活 SW_SHOWNORMAL=1, // 正常恢复并显示 SW_SHOWMINIMIZED=2, // 最小化并激活 SW_SHOWMAXIMIZED=3, // 最大化并激活 SW_HIDE=0, // 隐藏并激活另一窗口 SW_SHOW=5, // 按当前大小位置显示 }; [/CODE] 导入 shell32.dll 后就能直接调用。若执行失败,可配合 GetLastError 读错误码排查——外汇与贵金属交易外接程序涉及系统权限,高风险,建议在策略测试器之外的实盘前于演示账户先验证调用稳定性。

MQL5 / C++
class=class="str">"cmt">//---
Sleep(sleeping);
class=class="str">"cmt">//--- x64
class="type">long ShellExecuteW(
   class="type">long hwnd,               class=class="str">"cmt">//
   class="type">class="kw">string lpOperation,      class=class="str">"cmt">//
   class="type">class="kw">string lpFile,           class=class="str">"cmt">//
   class="type">class="kw">string lpParameters,     class=class="str">"cmt">//
   class="type">class="kw">string lpDirectory,      class=class="str">"cmt">//
   class="type">int nShowCmd             class=class="str">"cmt">//
);
class=class="str">"cmt">//--- x32
class="type">int ShellExecuteW(
   class="type">int hwnd,                class=class="str">"cmt">//
   class="type">class="kw">string lpOperation,      class=class="str">"cmt">//
   class="type">class="kw">string lpFile,           class=class="str">"cmt">//
   class="type">class="kw">string lpParameters,     class=class="str">"cmt">//
   class="type">class="kw">string lpDirectory,      class=class="str">"cmt">//
   class="type">int nShowCmd             class=class="str">"cmt">//
);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 启动应用程序的枚举命令                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum EnSWParam
  {
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 把窗口显示为最小化窗口,这个值是类似于 |
   class=class="str">"cmt">//| SW_SHOWMINIMIZED, 除了窗口没有被激活。                        |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_SHOWMINNOACTIVE=class="num">7,
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 激活并显示一个窗口,如果窗口被最小化或者      |
   class=class="str">"cmt">//| 最大化, 系统会把它恢复到它原来的大小和      |
   class=class="str">"cmt">//| 位置。应用程序应该指定这个标志,当          |
   class=class="str">"cmt">//| 第一次显示窗口的时候。                                |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_SHOWNORMAL=class="num">1,
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 激活窗口并把它显示为最小化窗口                        .    |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_SHOWMINIMIZED=class="num">2,
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 激活窗口并把它显示为最大化窗口。                          |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_SHOWMAXIMIZED=class="num">3,
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 隐藏窗口并激活另一个窗口。                                |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_HIDE=class="num">0,
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 激活窗口并以它当前的大小                          |
   class=class="str">"cmt">//| 和位置显示。                                                |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   SW_SHOW=class="num">5,
   };
class="macro">#class="kw">import  "shell32.dll"
class="type">int  GetLastError();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ShellExecute 函数                                            |
class=class="str">"cmt">//| 对指定文件进行某项操作                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- x64
class="type">long ShellExecuteW(class="type">long hwnd,class="type">class="kw">string lpOperation,class="type">class="kw">string lpFile,class="type">class="kw">string lpParameters,class="type">class="kw">string lpDirectory,class="type">int nShowCmd);
class=class="str">"cmt">//--- x32

用 ShellExecuteW 拉起多个从端终端

在 MQL5 里直接拉起外部程序,得走 WinAPI 的 ShellExecuteW。声明原型时 hwnd 传 0 即可,lpOperation 填 "open",lpFile 是从端 terminal64.exe 路径,lpParameters 把配置文件路径喂进去,nShowCmd 用枚举控制窗口状态。 下面这段枚举把常用显示参数固化了:SW_HIDE=0 是隐藏窗口并激活别的窗口;SW_SHOWNORMAL=1 首次显示用,恢复原有大小位置;SW_SHOWMINIMIZED=2 最小化激活;SW_SHOWMAXIMIZED=3 最大化激活;SW_SHOW=5 按当前尺寸位置显示;SW_SHOWMINNOACTIVE=7 最小化但不抢焦点。回测多账户联调时,7 这个值能避免主端被从端窗口夺焦。 实际拉从端不能一口气全起,得用 Sleep 错开。代码片段里每起一个终端前 Sleep(sleeping) 一次,连续拉了 4 个从端(路径变量 ExtInstallationPathTerminal_1 到 _4),各自读 Files 下的同一 common_file_name 配置。sleeping 若设 500,四端启动总耗时倾向在 2 秒量级,具体看机器 IO。 外汇与贵金属多端并行属于高负载高风险操作,配置文件路径写错会令从端空跑。开 MT5 把 LaunchSlaveTerminal 的参数换成你本机安装路径与 AppData 下的真实哈希目录,先单端验证再批量拉。

MQL5 / C++
class="type">int ShellExecuteW(class="type">int hwnd,class="type">class="kw">string lpOperation,class="type">class="kw">string lpFile,class="type">class="kw">string lpParameters,class="type">class="kw">string lpDirectory,class="type">int nShowCmd);
class="macro">#class="kw">import
class="macro">#class="kw">import "kernel32.dll"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 启动应用程序的枚举命令                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum EnSWParam
  {
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 把窗口显示为最小化窗口,这个值是类似于 |
  class=class="str">"cmt">//| SW_SHOWMINIMIZED, 除了窗口没有被激活。                          |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_SHOWMINNOACTIVE=class="num">7,
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 激活并显示一个窗口,如果窗口被最小化或者 |
  class=class="str">"cmt">//| 最大化, 系统会把它恢复到它原来的大小和 |
  class=class="str">"cmt">//| 位置。应用程序应该指定这个标志,当 |
  class=class="str">"cmt">//| 第一次显示窗口的时候。                                          |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_SHOWNORMAL=class="num">1,
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 激活窗口并把它显示为最小化窗口                        .          |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_SHOWMINIMIZED=class="num">2,
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 激活窗口并把它显示为最大化窗口。                                |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_SHOWMAXIMIZED=class="num">3,
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 隐藏窗口并激活另一个窗口。                                      |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_HIDE=class="num">0,
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 激活窗口并以它当前的大小 |
  class=class="str">"cmt">//| 和位置显示。                                                    |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  SW_SHOW=class="num">5,
  };
class=class="str">"cmt">//---
    Sleep(sleeping);
    LaunchSlaveTerminal(ExtInstallationPathTerminal_1,slaveTerminalDataPath1+"\\MQL5\\Files\\"+common_file_name);
    Sleep(sleeping);
    LaunchSlaveTerminal(ExtInstallationPathTerminal_2,slaveTerminalDataPath2+"\\MQL5\\Files\\"+common_file_name);
    Sleep(sleeping);
    LaunchSlaveTerminal(ExtInstallationPathTerminal_3,slaveTerminalDataPath3+"\\MQL5\\Files\\"+common_file_name);
    Sleep(sleeping);
    LaunchSlaveTerminal(ExtInstallationPathTerminal_4,slaveTerminalDataPath4+"\\MQL5\\Files\\"+common_file_name);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
}
LaunchSlaveTerminal("C:\Program Files\MetaTrader class="num">5 class="num">1\",
"C:\Users\KVN\AppData\Roaming\MetaQuotes\Terminal\038C9E8FAFF9EA373522ECC6D5159962\MQL5\Files\myconfiguration.ini");

◍ 代理连不上时的日志与应对

在 MT5 终端直接启动策略测试,而测试代理没连上的时候,『日志』页会留下一串特定记录。上面这段就是典型的失败样本:Core 1 在 15:10:49 连到 127.0.0.1:3002,约 11 秒后报『测试器代理认证错误』,随后连接关闭。 出现这类状况,优先怀疑本地资源争用或启动节奏太紧。实操上建议调大终端运行延迟(即代码里的 sleeping 变量),同时关掉一切可能占满 CPU 内核的重负载程序,给代理握手留余地。 从日志也能看到,重新拉起 MetaTester 5 x64 build 1368 后,服务在 16:08:45 起、16:09:36 停,全程不到一分钟就收工——说明代理环境恢复后,测试机本身启停是干净的,问题多在前置连接环节。外汇与贵金属回测涉及高杠杆与滑点风险,任何代理异常都可能导致历史数据不完整,验证前务必确认日志无认证报错。

MQL5 / C++
class="num">2016.07.class="num">15 class="num">15:class="num">10:class="num">48.327 Tester  EURUSD: 历史数据开始于 class="num">2014.01.class="num">14 class="num">00:class="num">00
class="num">2016.07.class="num">15 class="num">15:class="num">10:class="num">49.212 Core class="num">1  代理的处理开始
class="num">2016.07.class="num">15 class="num">15:class="num">10:class="num">49.717 Core class="num">1  连接到 class="num">127.0.class="num">0.1:class="num">3002
class="num">2016.07.class="num">15 class="num">15:class="num">11:class="num">00.771 Core class="num">1  测试器代理认证错误
class="num">2016.07.class="num">15 class="num">15:class="num">11:class="num">01.417 Core class="num">1  连接关闭
class="num">2016.07.class="num">15 class="num">16:class="num">08:class="num">45.416 启动 MetaTester class="num">5 x64 build class="num">1368 (class="num">13 Jul class="num">2016)
class="num">2016.07.class="num">15 class="num">16:class="num">08:class="num">45.612 服务器  MetaTester class="num">5 开始于 class="num">127.0.class="num">0.1:class="num">3000
class="num">2016.07.class="num">15 class="num">16:class="num">08:class="num">45.612 启动初始化结束
class="num">2016.07.class="num">15 class="num">16:class="num">09:class="num">36.811 服务器  MetaTester class="num">5 停止
class="num">2016.07.class="num">15 class="num">16:class="num">09:class="num">38.422 Tester  关闭测试机

「四终端同步测试与 Win API 调用落点」

四种测试模式同时跑自选 EA 的任务到此结束:EA 启动后,几乎能在四个终端里同步看到回测过程,验证并行观察是否真的零延迟偏差,开 MT5 多开实例即可复现。 文中实际调用了几个 Win API 函数,这才是能直接抄进 EA 的硬货:CopyFileW 负责在 MQL5 沙盒内外倒文件;FindFirstFileW / FindNextFileW / FindClose 组合做目录检索与句柄释放;GetOpenFileNameW 拉起系统打开文件框;ShellExecuteW 直接起外部程序。 下载包 2552.zip 约 14.18 KB,含完整工程;外汇与贵金属回测属高风险,并行结果仅反映历史数据概率,不代表实盘倾向。 有读者在 Win10 开 UAC 环境下把终端放 C:\forex 而非 Program Files,写文件无权限冲突——若你用标准安装路径,也要注意沙盒目录限制。

用 common.ini 接管代理与优化开关

MT5 的测试器与云/本地代理网络,可以通过 common.ini 直接写配置来接管,不必每次在 GUI 里点选。2016 年 9 月那轮讨论里,用户就提出想用 ini 文件同时控制前向优化(forward)和本地代理+云代理的混合接入,官方确认这条路是通的。 实际落地时,common.ini 里对应的段落决定测试器启动行为:代理地址、端口、是否启用云、以及优化模式参数都能固化。对做批量回测的交易者来说,这意味着换机器或重装终端后,只要丢一个 ini 进配置目录,代理拓扑和前向优化偏好就自动复原。 外汇与贵金属回测本身属高风险验证环境,配置错误可能让优化跑在错误数据上;建议先在一台本地代理机器上手动跑通一次,再把文件复制到其他节点。

◍ 最后说句实在话

把横盘识别交给 ADX 在策略测试器里跑均值,你会拿到一组横盘与趋势行情的参考阈值,用来框定止损和目标区间比拍脑袋稳。 订单、余额净值、回撤负载三类指标同时挂进测试器,能直接看见实况成交与资金曲线变形,省掉翻日志的功夫。 文本文件存 EA 输入参数是个被低估的动作,动态数组和变量落盘后,换环境不复写窗口也能复原。 进化与遗传算法不是玄学,拿简单 EA 做预测器集合优化,参数空间搜索效率会明显好于网格穷举,但外汇和贵金属杠杆高,回测顺不代表实盘概率同权,开 MT5 自己跑一遍再信。

把多终端调度交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同订单模式下的回测分布提示,把重复劳动交给小布,你专注决策。

常见问题

按本文约束,主终端一定不能使用 /portable 参数,否则从终端无法正确识别主账户编号,四模式联动测试会直接失效。
一个作为主终端发指令,另外四个作为从终端分别跑四种订单生成模式,少一个就覆盖不全。
关掉游戏、媒体播放器等高占用程序,避免单核被堵导致某个从终端测试卡死。
小布目前内置的是模式差异诊断与分布提示,四终端并发调度仍需按本文的 Win API 脚本在本地终端实现。
检查文件是否写入从终端正确目录,且主账户已在对应终端激活过,配置才会被策略测试器读取。