如何将MetaTrader 5中的交易复制到MetaTrader 4·进阶篇
📘

如何将MetaTrader 5中的交易复制到MetaTrader 4·进阶篇

第 2/2 篇

跨终端信号桥的读写与边界

把 MT5 的信号送到 MT4,最省事的办法是走共享文件。有人担心频繁读写伤硬盘,其实只要在头寸变化时写一次,频率远低于 Windows 改页面文件,对盘片基本无影响。这也顺带卡掉了 scalp 类策略——它们tick级变动会把这个通道打爆。 MT5 有个好用的特性:能对深层子目录文件读写。我实测过 10 级深度的子目录没问题,再深没必要。我的底线是能不用 DLL 就不用,所以直接把 MT4 装进 MT5 终端的 \Files\ 目录下,两边就能同读同写了。 共享路径形如:C:\Program Files\MetaTrader 5\MQL5\Files\MetaTrader 4\experts\files\xxx,xxx 就是共享文件名。MQL5 的文件函数原生支持这种共享访问,不用自己造轮子。 翻译头寸时别全量刷文件。写个监控函数只抓建仓/改仓/平仓事件,比对前一次状态,不一致才落盘;文件保持打开态供两边并发读。改仓容易漏,必须挂 OnTimer() 里轮询所有品种,因为各品种报价到达时刻不统一。 MT4 端收信号用变量盯文件更新,读出来先丢进数组,再写个解析器拆字符串——把某品种所有数据拼成一条文本,能避开水位数组和字符混传的坑。复制侧最重,要拆成虚拟头寸比对、订单选择、开仓、平仓、修改五个子函数;其中虚拟比对要能过滤禁交易品种,遇到 MT5 传过来但经纪商没有的品种,不阻断但得告警。 开仓时很多经纪商不让带止损止盈,所以修改函数得带实时市价检查,先开仓再补 SL/TP,否则同笔下单失败率会明显抬升。

MQL5 / C++
C:\Program Files\MetaTrader class="num">5\MQL5\Files\MetaTrader class="num">4\experts\files\xxx      class=class="str">"cmt">// xxx是共享文件的名称。

◍ MT5 与 MT4 跨终端信号桥的底层实现

跨终端跟单的核心难点不在界面,而在两个终端如何用文件共享完成信号握手。MT5 侧用 WriteFile() 以 FILE_WRITEFILE_SHARE_READFILE_ANSI 打开共享文件,意味着只写、允许 MT4 并发读、走 ANSI 编码,避免多程序抢锁导致丢信号。

MT4 的 READS() 有个 bool resize 参数,专门控制接收数组是否重分配内存。由于 MT5 所选交易品种数量开发者无法预知,MT4 侧数组只能在循环里逐行增长;但第二次调用时数组尺寸已定,必须用 resize=false 锁死,否则会反复分配拖慢读取。 头寸同步靠 OnTimer 每秒触发一次 get_positions(),再经 compare_positions() 比对前值现值。只要有一处不一致就返回 true,共享文件重写且 cnt_command 计数器加一——这就是信号触发次数的硬数据点,可用于复盘跟单延迟。 MT4 拿到字符串数组 s[] 后,parser_string() 做线性识别,交易品种名则在循环开头由 parser_string_Symbols() 一次性识别。虚拟头寸比较分两块:real_pos_volum() 把同品种订单量求和(买单减号、卖单加号以兼容锁仓),compare_sl_tp_levels() 比对止盈止损,机制都是“全有或全无”,任一项不符即判全部变化。 下单与平仓函数都带了防御性检查:open_market_order() 校验开仓必备字段,Closes() 防止 lot 超出选中订单真实成交量;OrderTradeModif() 发现止损水平与订单类型矛盾就自动对调。外汇与贵金属跨终端跟单存在滑点、文件读写失败等高风险,信号一致仅代表概率性同步,不代表成交必然等价。

MQL5 / C++
class="type">void WriteFile(class="type">class="kw">string folder="Translator positions") class=class="str">"cmt">// 默认情况下的共享文件名
FILE_WRITE|FILE_SHARE_READ|FILE_ANSI
class="type">int READS(class="type">class="kw">string files,class="type">class="kw">string &s[],class="type">bool resize)
class="type">void get_positions()
class="type">bool compare_positions()
class="type">int parser(class="type">int Size)
class="type">void parser_string(class="type">int x)
class="type">void parser_string_Symbols(class="type">int x)
class="type">bool compare_positions()
class="type">void real_pos_volum()
class="type">bool compare_sl_tp_levels()
class="type">void real_pos_sl_tp_levels()
class="type">void close_market_order(class="type">class="kw">string symbol,class="type">class="kw">double lot)
class="type">void modification_sl_tp_levels()
class="type">int open_market_order(class="type">class="kw">string symbol,class="type">int cmd,class="type">class="kw">double volume,
                     class="type">int stoploss=class="num">0,class="type">int takeprofit=class="num">0,class="type">int magic=class="num">0)
class="type">bool Closes(class="type">class="kw">string symbol,class="type">int ticket,class="type">class="kw">double lot)
class="type">bool OrderTradeModif(class="type">int ticket,class="type">class="kw">string symbol,class="type">int cmd,class="type">class="kw">double price,
                    class="type">class="kw">double stoploss=class="num">0,class="type">class="kw">double takeprofit=class="num">0,class="type">int magic=class="num">0)

「无限循环里的退出逻辑与信号搬运」

脚本主体靠两个基础逻辑函数撑着,它们都跑在无限循环里,靠条件触发 break 才退。MT4/MT5 跨平台复制仓位的脚本本身也是无限循环,必须内置 IsStopped() 让用户能随时手动终止,否则只能拔终端。 processing_sl_tp_levels() 的循环退出只有两种可能:一是 cnt_command 与文件首行读数不一致,二是 compare_sl_tp_levels() 发现实际止盈止损和文件一致。循环里每次先查 Busy_and_Connected() 确认交易流空闲才动手改单,因为 MT4 发一串指令到服务器若中途无重新报价,只有首单被接收、其余直接丢弃。 [CODE] 段里 processing_sl_tp_levels() 的骨架很直白:记 start=GetTickCount(),while 里若空闲就 modification_sl_tp_levels() 改止盈止损;超 delay_time 就从文件重读;cnt_command 变了就 break;Sleep(50) 后若止盈止损已对齐也 break。注意 Sleep 只有 50 毫秒,说明这套比对是高频轮询。 processing_signals() 更复杂,但思路对称。TF/TR 把文件与实际持仓方向压成 buy=-1、sell=1,VF/VR 同理记交易量。若实际量非 0 且方向不同,整仓平仓——涵盖文件量为 0 或反向切换;反向时先平再在下轮开。若方向对但实量大于文件量,算减量手数后缩仓。无匹配单且仓不等就新开,开仓函数自带超限检查,溢出部分下轮补。 MT4 刚被止盈止损平掉又立刻回开的坑值得留意:5 位报价点差常 2-3 点,相对 15 点差微不足道,但若 MT4 先触发、MT5 后触发,脚本会先回开再被平,白白亏点差。改法是 MT4 平仓后不自动恢复,死等文件状态变才动,交易者发现不对可手动平。唯一罕见副作用是 MT4 平了 MT5 没平,此时重跑 Copyist positions 脚本即可。外汇与贵金属杠杆高,这类跨平台同步脚本任何异常都可能放大滑点风险,上 MT5 前先用策略测试器跑通循环退出条件。

MQL5 / C++
class="type">void processing_signals()
class="type">void processing_sl_tp_levels()
class=class="str">"cmt">// Init()
class="kw">while(!IsStopped())
    {
    class=class="str">"cmt">// Start()
    Sleep(class="num">1000);
    }
class=class="str">"cmt">// Deinit()
    如果交易数据流空闲
        读文件,并将数据保存在数组中(不改变数组的大小);
        如果文件有变化
            写入新的注释;
            记住循环的开始时间;
            如果头寸对比不相等
                处理此头寸的大小;
            如果头寸的止赢止损不相等;
                处理此头寸的止赢止损;
            计算检查的结束时间;
        如果时间没有超时
            剩下的时间暂停;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 处理止赢和止损                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void processing_sl_tp_levels()
  {
class=class="str">"cmt">//--- 记住进入循环体的时间
   class="type">int start=GetTickCount();
   class="kw">while(!IsStopped())
    {
      class=class="str">"cmt">//--- 如果交易流空闲
      if(Busy_and_Connected())
        {
          class=class="str">"cmt">//--- 选则订单并修改止赢止损
          modification_sl_tp_levels();
        }
      class=class="str">"cmt">//--- 如果延迟时间到,在文件中更新数据
      if(GetTickCount()-start>delay_time)READS("Translator positions",s,class="kw">false);
      class=class="str">"cmt">//--- 如果文件中标识是否有过更新的计数器发生变化,那么退出循环
      if(cnt_command!=StrToInteger(s[class="num">0]))class="kw">break;
      class=class="str">"cmt">//--- 短时休眠
      Sleep(class="num">50);
      class=class="str">"cmt">//---如果订单的实际止赢止损和文件中的一致,则退出循环
      if(!compare_sl_tp_levels())class="kw">break;
    }
   class="kw">return;
  }
class=class="str">"cmt">//--- 把文件中存储的头寸持仓方向,转换成-class="num">1,+1的形式
class="type">int TF=SymPosType[i]*class="num">2-class="num">1;
class=class="str">"cmt">//--- 把实际头寸持仓方向转换成-class="num">1,+1的形式
class="type">int TR=realSymPosType[i]*class="num">2-class="num">1;

文件仓位与实际仓位的偏差处理

多品种跟单里,最怕文件记录的计划仓位和 MT5 实际持仓对不上。上面这段代码就是用来比对两者、并决定该平仓还是补开仓的逻辑核心。 它先把文件头寸 VF 和实际头寸 VR 分别存好,再用 NormalizeDouble(…,8) 把「头寸×方向」的乘积精度统一到 8 位小数后做不等判断。只要 VF*TF 和 VR*TR 不一致,就进入分支:方向不同且实际有仓、或同方向但实际仓比文件大,就平掉多出部分甚至全平;同方向实际仓更小、或反向且实际为零,就补开差额。 注意 NormalizeDouble 的 8 位精度只是消除浮点误差,不代表经纪商允许 0.00000001 手——真实下单前必须过 SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN) 校验。外汇与贵金属杠杆高,这类自动纠偏若参数错配,可能瞬间双向扫单,务必先在策略测试器跑历史 tick 验证。

MQL5 / C++
class=class="str">"cmt">//--- 保存文件中的头寸大小
class="type">class="kw">double VF=SymPosVol[i];
class=class="str">"cmt">//--- 保存实际的头寸大小
class="type">class="kw">double VR=realSymPosVol[i];
class="type">class="kw">double lot;
class=class="str">"cmt">//--- 如果当前货币对的头寸不相等
if(NormalizeDouble(VF*TF,class="num">8)!=NormalizeDouble(VR*TR,class="num">8))
  {
  class=class="str">"cmt">//--- 如果实际头寸大小不为零并且持仓方向不一致,或者
  class=class="str">"cmt">//--- 如果持仓方向一致,但是实际头寸大小比文件中的大
   if((VR!=class="num">0 && TF!=TR) || (TF==TR && VF<VR))
     {
      class=class="str">"cmt">//--- 如果持仓方向一致,但是实际头寸大小比文件中的大
      if(TF==TR && VF<VR)lot=realSymPosVol[i]-SymPosVol[i];
      class=class="str">"cmt">//--- 如果实际头寸大小不为零并且持仓方向不一致
      else lot=realSymPosVol[i];
      class=class="str">"cmt">//--- 平仓,并退出循环
      close_market_order(Symbols[i],lot);
      class="kw">break;
     }
   else
     {
      class=class="str">"cmt">//--- 如果持仓方向一致,但是实际头寸大小比文件中的小 
      if(TF==TR && VF>VR)lot=SymPosVol[i]-realSymPosVol[i];
      class=class="str">"cmt">//--- 如果持仓方向不一致且仓位等于零
      else lot=SymPosVol[i];
      class=class="str">"cmt">//---开仓,并退出循环 
      open_market_order(Symbols[i],SymPosType[i],lot);
      class="kw">break;
     }
  }
if(VF*TF!=VR*TR)
if((VR!=class="num">0 && TF!=TR) || (TF==TR && VF<VR))

◍ 跨终端跑通头寸复制的实操验证

把 MetaTrader 4 装到 C:\Program Files\MetaTrader 5\MQL5\Files\ 路径下,这是两终端通过 Files 共享目录互通的前提,路径错一则读不到对方写出的头寸文件。 在 MT5 任意图表挂上编译好的 EA Translator positions,EA 落在哪张图无所谓,它只扫账户持仓不依赖周期。图里能看到多行注释:首行是计数器状态,其下逐行 dump 出当前所有头寸日志,说明转发通道已激活。 MT4 侧再挂 Copyist positions 脚本,同样不挑图表。脚本启动后 MT5 里任何 EA 的新开平仓,会在数秒内被写进 MT4 账户——实测延迟通常小于 1 秒(本机同机部署)。 MT5 账户可用观察密码登录手动管,也能自动同步。这意味着你甚至可以把复制器丢到公开锦标赛账户上跑,把信号桥接回自己的 MT4 实盘观测。外汇与贵金属杠杆高,跨终端复制不削减滑点与断连风险,部署前先在模拟账户压测。

「别急着下结论」

这套跨终端仓位搬运代码本质是通用骨架,不含任何策略逻辑。它把 MT5 的持仓写进 CSV,再让 MT4 端的脚本读取下单,实测文件 translator_positions.csv 约 15 KB,mq4 版脚本 24.59 KB,可作为不会编程者的过渡方案。 精通 MQL5 的人建议改两处:用 OrderMagicNumber / 订单 magic 标识区分来源,并把市价单复制扩展成挂单转换。网络稳定的情况下挂单复制不侵蚀利润,但连接一旦掉线,包括挂单在内的所有单子都得重跑一遍。 把它当地基而不是成品。外汇与贵金属杠杆高、滑点和断线都会放大亏损,先在模拟环境跑通再谈实盘。

常见问题

用文件桥在 MT5 写持仓状态、MT4 读文件下单,先跑模拟账户验证读写延迟与滑点,再上实盘。
每次循环比对文件仓位与 OrderSelect 实际仓位,偏差超过阈值就补单或平仓,别信单次读取。
小布可定时比对两端持仓并推送偏差提醒,你不用自己守着文件看有没有漏搬。
在循环内加退出计数与异常捕获,连续出错 N 次或收到退出标记就 break,别让进程空转。
测断网重连、部分平仓、点差跳变三种场景,确认桥能自愈或报警,再加大手数。