如何将MetaTrader 5中的交易复制到MetaTrader 4·进阶篇
跨终端信号桥的读写与边界
把 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,否则同笔下单失败率会明显抬升。
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_WRITE | FILE_SHARE_READ | FILE_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() 发现止损水平与订单类型矛盾就自动对调。外汇与贵金属跨终端跟单存在滑点、文件读写失败等高风险,信号一致仅代表概率性同步,不代表成交必然等价。
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 前先用策略测试器跑通循环退出条件。
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 验证。
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 标识区分来源,并把市价单复制扩展成挂单转换。网络稳定的情况下挂单复制不侵蚀利润,但连接一旦掉线,包括挂单在内的所有单子都得重跑一遍。 把它当地基而不是成品。外汇与贵金属杠杆高、滑点和断线都会放大亏损,先在模拟环境跑通再谈实盘。