使用MetaTrader 5作为MetaTrader 4的信号提供者(基础篇)
「用 MT5 给 MT4 当信号源」
想在 MT4 上跟单,但策略只在 MT5 跑?MetaTrader 5 可以作为 MetaTrader 4 的信号提供者,把 MT5 账户的持仓通过信号服务推送到订阅的 MT4 终端。 这套机制在 2013 年 9 月 2 日就有公开示例,原帖获 4239 次浏览、16 条讨论,作者 Karlis Balcers 验证了跨终端信号转发的可行性。 实际落地时,MT5 端需开启信号发布权限并设定分成比例,MT4 端在信号选项卡搜索对应提供者即可订阅;外汇与贵金属杠杆品种波动剧烈,跨终端延迟可能造成滑点,订阅前建议用模拟账户验证转发稳定性。
◍ 为什么要在 MT5 上折腾信号中转
MetaTrader 5 发布多年,但不少交易者在真实账户用不上它:一类人用 MQL5 写策略回测漂亮,却卡在经纪商不开实盘权限;另一类人手动交易,却更认 MT5 的订单组织逻辑而非 MT4。 自动交易锦标赛里,跟单需求一直很硬——有人自己撸了跟单脚本,更多人想逼近领先者的成交质量,还愁资金怎么分配。 还有一批人策略能打,想同时推给朋友或多端接收,又怕实时发信号把延迟和滑点搞崩。这三件事逼出一个共同点:得有个不降效的中转方案,后面就拆它。
用只读模式实时抓取选手交易流
MQL5 锦标赛首页的跟随玩法有个硬伤:数据每五分钟才刷新一次,真实账户跟单时很容易错过最佳进出场窗口。作者实测在真实账户跟首页活动已吃到利润,但五分钟延迟让效率大打折扣,论坛里同样做法的人多了还会给首页灌爆流量。 转机在 MT5 的“只读”模式(禁止交易)能直接连到每个选手账户。作者写了个 EA 在仅只读权限下跑,意外发现不仅能跑,还能拿到仓位、订单和已成交交易的信息。这一步打通了实时获取选手每一笔交易动作的路径,比五分钟轮询首页靠谱得多。 外汇与贵金属杠杆高、波动剧烈,跟单再实时也只是降低延迟,不消除方向误判风险,真要上实盘先拿小资金验证只读 EA 的稳定性。
「跟单该抓订单还是交易」
要把 MT5 的信号发去 MT4,得先想清楚 MT4 里有哪些交易类型。如果只盯“仓位”,除非每秒比对一次仓位状态或按订单号逐一查,否则拿不到账户全部动作,信息是不完整的。 更靠谱的做法是直接跟“订单”或“交易”。订单比交易早一步出现,还带挂单内容,但它缺了 ENUM_DEAL_ENTRY 这个进场类型字段。 DEAL_ENTRY_TYPE 能告诉你账户在订单并行计算时到底发生了什么。所以理论上把“交易”和“订单”综合起来最稳:既有挂单信息,也能捕捉账户真实行为。不过不同经纪商的点差和滑点不一样,纯靠挂单推算容易算错。 实测中只跟“交易”也能跑挂单,只是受网络影响有微小延迟。我在速度(挂单)和效果(交易)之间选了效果,跟交易。外汇和贵金属杠杆高,信号延迟可能放大滑点风险,开 MT5 用下面的枚举确认一下你经纪商的 deal entry 分布再决定跟哪种。
◍ 用 TCP 把 MT5 信号甩给外部客户端
要让别的机器上的程序也能收到 MT5 的策略信号,走 TCP 是最省事的。MQL5 本身没有现成的 socket API,社区里有人用 WinInet.dll 做终端间数据交换,但实际跑起来限制多,不够顺手。 熟悉 C# 的话,可以自己封一个非托管导出的库挂进 MT5。参考非托管导出那套兼容方案,服务端用后台独立线程跑,实测能撑住最多 500 个并发客户端,而且不阻塞 EA 的主循环。本机只要装了 .NET Framework 3.5 或以上(多数 Windows 已带)就能直接跑。 下面这段是 MQL5 侧导入 SocketServer.dll 的声明,以及 C# 里 WaitForClients 的核心监听片段。 #import "SocketServer.dll" string About(); // 返回函数库版本与说明信息 int SendToAll(string msg); // 向当前所有已连客户端广播文本 bool Stop(); // 关闭服务器 bool StartListen(int port);// 在指定端口监听,上限500连接,全异步 string ReadLogLine(); // 取一行服务端日志,缓冲最多100行 #import internal static void WaitForClients() { if (server != null) { Debug("无法开始侦听!服务器没有清空."); return; } try { IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, iPort); server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); server.Bind(localEndPoint); server.Listen(500); isServerClosed = false; isServerClosedOrClosing = false; while (!isServerClosedOrClosing) { allDone.Reset(); server.BeginAccept(new AsyncCallback(AcceptCallback), server); allDone.WaitOne(); } } catch (ThreadAbortException) { } catch (Exception e) { Debug("WaitForClients() 错误: " + e.Message); } finally { if (server != null) { server.Close(); server = null; } } } 在 MT5 里用 StartListen(某个端口) 拉起后,EA 里调 SendToAll("BUY EURUSD 0.1") 这类字符串,远端客户端就可能实时收到。外汇与贵金属杠杆高、滑点跳空频繁,信号传输延迟或丢包都可能让执行价偏离预期,实盘前务必在模拟环境压测连接稳定性。
class="macro">#class="kw">import "SocketServer.dll" class=class="str">"cmt">// 使用Cclass="macro">#语言开发的函数库(根据 [MQL5官方文档] 文章的相关信息开发) class="type">class="kw">string About(); class=class="str">"cmt">// 函数库的“关于”信息. class="type">int SendToAll(class="type">class="kw">string msg); class=class="str">"cmt">// 给所有客户发文本信息. class="type">bool Stop(); class=class="str">"cmt">// 停止服务器. class="type">bool StartListen(class="type">int port); class=class="str">"cmt">// 开启服务器. 听所有进来的连接(最多500个客户端). class=class="str">"cmt">// 所有客户连接使用异步线程开发. class="type">class="kw">string ReadLogLine(); class=class="str">"cmt">// 读取服务器记录 (可以包含错误和其他信息). class=class="str">"cmt">// 读取是可选的. 服务器最多存100行记录. class="macro">#class="kw">import internal class="kw">static class="type">void WaitForClients() { if (server != null) { Debug("无法开始侦听!服务器没有清空."); class="kw">return; } try { IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, iPort); server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); server.Bind(localEndPoint); server.Listen(class="num">500); isServerClosed = false; isServerClosedOrClosing = false; class="kw">while (!isServerClosedOrClosing) { allDone.Reset(); server.BeginAccept(new AsyncCallback(AcceptCallback), server); allDone.WaitOne(); } } catch (ThreadAbortException) { } catch (Exception e) { Debug("WaitForClients() 错误: " + e.Message); } finally { if (server != null) { server.Close(); server = null; } } }
异步接收连接时的客户端登记动作
在 MT5 的异步 TCP 监听里,AcceptCallback 是 listener 收到新连接后的回调入口。它先调用 allDone.Set() 释放主线程的等待信号,随后立刻判断 isServerClosedOrClosing——若服务端已进入关闭流程则直接 return,避免半开状态下还接客。 真正建立连接时,EndAccept 从 IAsyncResult 取出 client 套接字。若全局 clients 容器不为空,就用 lock 加锁做线程安全扩容:Array.Resize 把数组长度 +1,新槽位写入 socket、RemoteEndPoint 转出的 ip 字符串,以及 alive=true 的存活标记。 扩容后打印「客户端已连接:」加 IP,方便你在专家日志里核对连入来源。异常分支只做 Debug 输出,不抛不中断,因此单机模拟行情转发时,偶发连接抖动通常不会拖垮监听循环。 这段逻辑跑在独立线程,clients 数组每次 +1 的线性扩容在百级并发内开销可忽略;若你打算接几十个终端做多账号同步,建议把 Array.Resize 换成 List<ClientInfo> 再观察延迟。
isServerClosed = true; isServerClosedOrClosing = true; } } internal class="kw">static class="type">void AcceptCallback(IAsyncResult ar) { try { allDone.Set(); if (isServerClosedOrClosing) class="kw">return; Socket listener = (Socket)ar.AsyncState; Socket client = listener.EndAccept(ar); if (clients != null) { lock(clients) { Array.Resize(ref clients, clients.Length + class="num">1); clients[clients.Length - class="num">1].socket = client; clients[clients.Length - class="num">1].ip = client.RemoteEndPoint.ToString(); clients[clients.Length - class="num">1].alive = true; Debug("客户端已连接: " + clients[clients.Length - class="num">1].ip); } } } catch (Exception ex) { Debug("AcceptCallback() 错误: " + ex.Message); } }