简介:面向MT4平台有跟单需求的交易者,这款本地跟单EA可实现多个账户间同步下单,尤其适合希望跟随优质观摩账户信号、但又不想手动重复操作的投资者。资源包共8个文件,内含3个ex4程序,分别对应正向跟单、反向跟单及喊单模块,另配有4个txt参数或操作说明和1个html使用指引,整体仅43KB,轻量且便于快速部署。目前已有937人学习/下载。功能层面,EA支持买卖单及挂单的开立、修改、关闭全流程跟单,可设置固定手数或按资金比例跟单,并已适配4/5位报价差异及不同品种标识的平台。反向跟单还特别补偿了点差因素,适合用于风险对冲或镜像策略;配套文档对参数填写和常见问题作了说明,即使没有MQL编程经验也能参照完成配置,可直接应用于模拟盘或小资金实盘验证。 做MT4跟单EA这个话题,其实市面上讨论的人不少,但大多数帖子停留在“能跟单”的层面,很少把数据链路、订单同步机制、故障恢复这些真正影响稳定性的细节讲透。我自己从MQL4入门到做出一套多账户跟单系统花了挺长时间,中间踩过不少坑,也推翻过好几版设计。这篇就把我实际做MT4跟单EA的思路、关键代码逻辑和排障经验整理出来,希望能给正在做类似项目的朋友一些参考。
1. 跟单方案的整体架构与设计思路
1.1 跟单EA到底在解决什么问题
MT4平台本身不提供跨账户的自动订单复制功能。如果你同时操作几个账户,要么手动来回切换下单,要么就用第三方服务。所谓跟单EA,本质上就是把一个“信号源账户”(通常叫主账户)的订单行为,实时同步到其他“跟随账户”上——主账户开多,跟随账户自动开多;主账户平仓,跟随账户也平仓。
常见的使用场景有这么几类:
- 交易信号社区:有人在主账户跑策略,订阅者同步跟着下单。
- 多账户管理:自己同时维护多个账户,不想重复操作。
- 策略对比:同一套策略在不同账户、不同杠杆下跑,检验参数适应度。
跟单EA最核心的价值不是“自动下单”,而是把订单事件变成一种可复制的数据流,在极短时间内传达到多个终端并执行。想清楚了这一点,整个系统的设计就会围绕“事件采集、传输、执行”展开。
1.2 三种数据链路选型对比
MT4 EA之间要通信,无外乎三种路子:文件同步、数据库共享、Socket直连。我三种都试过,列个表对比一下:
| 方案 | 实时性 | 复杂度 | 跨平台 | 稳定性 | 适合场景 |
|---|---|---|---|---|---|
| 文件(CSV/INI) | 秒级~毫秒级 | 低 | 仅本机 | 一般 | 单机多账户跟单 |
| 数据库(MySQL/SQLite) | 毫秒级 | 中 | 局域网 | 中等 | 局域网内跨机器 |
| Socket通信 | 毫秒级 | 高 | 局域网/公网 | 高 | 远程、多账户、需要心跳 |
文件方案最简单,一个EA写文件,另一个EA定时读文件。但问题是频繁读写文件容易造成锁冲突,而且Tick频率上来之后,文件的写入延迟和读取延迟会导致明显漏单。数据库方案需要额外装MySQL或SQLite,MT4自带的是DLL调用,部署环境麻烦一些,胜在数据可以持久化,方便排查。
我最终选了Socket方案,主要的考量有两点:
第一,Socket通信是事件驱动的,数据到了就处理,不用轮询,延迟更低; 第二,TCP连接自带ACK确认机制,数据完整性比文件写入更可靠,跟单场景最怕的就是“少一个单没跟上”。
下面是我实际用的架构图,这里我用文字描述一下:
主账户MT4终端 -> 主账户EA(信号采集) -> Socket客户端 -> 服务器/直连 -> 跟随账户EA(指令执行) -> 跟随账户MT4终端早期版本我没有中间服务器,两个终端直接点对点连接。后来账户多了,改用了一台轻量服务器做转发,方便统一管理客户端状态。
2. 核心细节解析与实操要点
2.1 主账户端:订单事件的捕获与信号设计
主账户EA的工作核心是监听订单变化。MQL4里最常用的方式是OrderSelect配合OrderMagicNumber和OrdersTotal()遍历。
我第一版写的是每次Tick都把所有订单拉一遍,跟上一状态对比,发现新增就广播。这种方式能跑,但问题在于:订单状态多(开仓、平仓、修改止损止盈、删除挂单),状态机的逻辑会越写越复杂。
后来我换了一种思路:以订单生命周期作为信号源,而不是以状态差作为触发点。
具体做法是:
- 在
OnTick()里只维护一个订单集合,记录已处理过的订单票号。 - 新票号出现 -> 广播“开仓信号”。
- 旧票号消失 -> 广播“平仓信号”。
- 票号还在但止损止盈变了 -> 广播“修改信号”。
每个信号用固定的字符串格式打包,例如:
ACTION|TICKET|SYMBOL|TYPE|VOLUME|OPENPRICE|SL|TP|MAGIC|COMMENT这个格式是主从账户之间的“协议”,两边必须严格一致,否则解析环节就会出问题。
2.2 跟随账户端:订单执行与容错
跟随端EA收到信号后,要做几步校验:
- 品种是否存在,当前图表是否有该品种;
- 订单类型是市价单还是挂单,分别走不同分支;
- 手数是否需要换算(主账户1手,跟随账户可能因为资金量要按比例缩小);
- 当前是否有未完成的同策略订单,避免重复开仓;
- 执行下单后,记录该订单映射关系,用于后续平仓同步。
这里有个容易被忽略的细节:跟随账户的开仓价和主账户存在价差,因为两个账户的报价来源(服务器)不同。所以跟单逻辑里,平仓时不能按主账户的平仓价去设置跟随账户的止盈止损,而应该用当下的市场报价去触发平仓动作。
如果直接拿主账户的平仓价发给跟随端,会发生一种奇怪的现象:跟随账户在某个价格执行平仓,但滑点导致成交价不同,后续订单记录对不上,最终统计收益时两边的盈亏会有出入。
2.3 品种映射与手数计算逻辑
不同MT4券商的品种名称不统一,EURUSD在有的平台叫EURUSD,在有的平台叫EURUSD.m或EURUSDpro。跟单时必须做一张映射表,否则主账户发来EURUSD,跟随端查不到对应品种,直接跳过就漏单了。
手数计算我给两个方案:
| 方案 | 说明 | 优势 | 劣势 |
|---|---|---|---|
| 固定比例 | 主账户0.1手,跟随端固定0.1手 | 简单直接 | 账户资金不同时风险不平衡 |
| 按净值比例 | 根据主/从账户净值占比自动换算 | 风险一致性好 | 需要实时拉取账户净值,逻辑复杂 |
我在实际项目里用的是“净值比例 + 手数最小单位取整”。公式:
目标手数 = 主账户手数 × (从账户净值 / 主账户净值) × 杠杆修正系数 目标手数 = NormalizeDouble(目标手数, 2)要注意的是,如果算出来的目标手数小于平台最小手数(比如0.01),这个单就放弃,并在日志里记录原因,方便事后排查。
3. 实操流程与核心代码实现
3.1 主账户端信号采集核心逻辑
直接贴一段核心代码,这是主账户端EA的精简版:
string prevOrders[]; // 上一轮订单集合 int prevCount = 0; void OnTick() { int total = OrdersTotal(); string currOrders[]; int currCount = 0; for(int i = total - 1; i >= 0; i--) { if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { // 只处理非魔术号的真实订单 if(OrderMagicNumber() != MagicNumber) continue; string key = IntegerToString(OrderTicket()) + "|" + OrderTicket(); currOrders[currCount++] = key; } } // 对比旧集合,找出新增订单 for(int i = 0; i < currCount; i++) { bool isNew = true; for(int j = 0; j < prevCount; j++) { if(currOrders[i] == prevOrders[j]) { isNew = false; break; } } if(isNew) { // 解析这笔订单并广播 if(OrderSelect(FindTicket(currOrders[i]), SELECT_BY_TICKET)) { string signal = BuildSignal(OrderTicket(), OrderSymbol(), OrderType(), OrderLots(), OrderOpenPrice(), OrderStopLoss(), OrderTakeProfit()); SendSignal(signal); } } } prevCount = currCount; ArrayResize(prevOrders, prevCount); for(int i = 0; i < prevCount; i++) prevOrders[i] = currOrders[i]; } string BuildSignal(int ticket, string symbol, int type, double lots, double openPrice, double sl, double tp) { return StringFormat("ACTION|%d|%s|%d|%.2f|%.5f|%.5f|%.5f", ticket, symbol, type, lots, openPrice, sl, tp); }这里用字符串ACTION|前缀,是因为我后来的版本里加了一个“心跳”信号(PING|),用来双方确认连接还活着。如果后面要扩展其他信号类型,只要保持前缀一致就行。
3.2 跟随端订单执行与平仓映射
跟随端收到“开仓”信号后,不能直接OrderSend就完事,要记录一个映射表,把主账户的票号映射到跟随账户的票号:
struct FollowMapping { int masterTicket; int slaveTicket; string symbol; }; FollowMapping mapping[]; void OnSignal(string signal) { string parts[]; int count = StringSplit(signal, '|', parts); if(count < 7) return; string action = parts[0]; if(action == "ACTION") { int masterTicket = StrToInteger(parts[1]); string symbol = parts[2]; int type = StrToInteger(parts[3]); double lots = StrToDouble(parts[4]); double openPrice = StrToDouble(parts[5]); double sl = StrToDouble(parts[6]); double tp = StrToDouble(parts[7]); int slaveTicket = ExecuteOrder(symbol, type, lots, sl, tp); if(slaveTicket > 0) { // 记录映射 int idx = ArraySize(mapping); ArrayResize(mapping, idx + 1); mapping[idx].masterTicket = masterTicket; mapping[idx].slaveTicket = slaveTicket; mapping[idx].symbol = symbol; } } else if(action == "CLOSE") { // 平仓:根据信号里的主账户票号找到跟随端票号 int closeTicket = FindSlaveByMaster(StrToInteger(parts[1])); if(closeTicket >= 0) { CloseOrder(closeTicket); } } }平仓信号里必须带上主账户的原始票号,否则跟随端不知道要平哪个单。这就是映射表的用途。
还有一种是“反向平仓”——主账户一笔单子把之前的对应单平掉了,但跟随端可能同时存在多个同品种同方向的单子,这时如果只是简单找一张单,可能平错单。我后来加了一个策略:优先挑开仓时间最接近的订单平掉,然后再更新映射。
3.3 参数配置与部署检查清单
| 参数名 | 建议值 | 说明 |
|---|---|---|
| MagicNumber | 20250101 | 必须保证主从端一致 |
| ServerIP | 你的服务器IP | 跟随端填主账户的IP或中转服务器 |
| ServerPort | 8890 | 建议选一个大端口的自定义端口 |
| FollowLotsMode | 1(比例)/2(固定) | 根据实际需求切换 |
| SymbolMapping | EURUSD=EURUSD.m | 按券商实际品种名填写 |
| HeartbeatInterval | 5秒 | 心跳间隔,用于断线重连 |
部署的时候有几个点容易被忽略,写出来提醒一下:
- MT4的“允许DLL导入”必须勾选,否则Socket通信API无法调用。
- Windows防火墙要放行对应的端口,否则外部连接直接超时。
- 如果服务器在海外,客户端和服务器之间的网络抖动会直接影响跟单延迟,建议主从之间开通好一点的网络线路再做高频跟单。
4. 常见问题与排查技巧实录
4.1 漏单问题
漏单是跟单EA最常被吐槽的问题。排查方向我按优先级排序:
- 网络是否断过?断线期间信号发没发出去?
- 跟随端是否刚好在重启MT4?重启期间信号没被消费。
- 手数是否低于平台最小可开仓手数?比如主账户0.01手,跟随端账户不能开0.001手,导致拒单。
- 品种映射是否存在?跟随端没有该品种的行情数据,直接跳过。
我自己的经验是,网络断线导致的漏单占了绝大多数,所以我花了比较多精力在“补偿同步”功能上:每次重连后,跟随端不再只接收实时信号,而是主动向主账户请求一份“当前全量持仓列表”,然后跟本地的持仓做差集,把缺失的单子补齐。
4.2 订单重复开仓
重复开仓的原因通常是主账户端状态判断有误,信号发了两遍,跟随端也执行了两遍。
一个有效的防护机制是:跟随端在收到信号后,先查一下当前的订单池,如果存在“相同品种、相同方向、相同手数、开仓时间距离当前不超过X秒”的单子,就认为重复信号,忽略掉。
4.3 滑点与平仓价差
实际跟单中,跟随端的成交价和主账户差距在正常行情下不会太大,但在新闻时段,价差可能拉到几十个点。如果你做的是剥头皮策略,这种微小的价差会让跟随端的盈利大幅缩水。
我用的一个缓解方案是:跟随端的止损止盈不直接照抄主账户的数值,而是加上“点的偏移补偿”。比如主账户止损设50点,跟随端止损设52点,留出一点点容错空间,防止因为微小价差导致跟随端的单子被非预期扫损。
4.4 常见问题速查表
| 现象 | 可能原因 | 检查方法 | 解决方案 |
|---|---|---|---|
| 主账户开仓,跟随账户不动 | 网络断开 / 端口不通 / 状态未同步 | 查看跟随端日志是否有连接失败记录 | ping服务器IP,检查防火墙,检查心跳是否正常 |
| 跟随账户重复开仓 | 信号重发 / 状态判断逻辑bug | 查看日志中相同信号出现次数 | 增加去重逻辑:最近N秒内同品种同方向只开一单 |
| 平仓不对应 | 映射表未建立 / 跟单端重启丢失映射 | 检查memory中mapping数组 | 在跟随端重启后从主账户拉取全量持仓重建映射 |
| 手续费过高 | 跟单手数多大 / 频繁开平 | 对比主从收益 | 调整净值比例参数 |
4.5 关于日志和调试的建议
开发阶段我一定会开一个独立日志文件,把每个收到的原始信号和每个下发的订单都记录下来。这样出问题时,比对主账户和跟随账户的日志,问题基本能定位到是哪一环。
另外,MQL4的Print()在MT4自带的“智能交易系统”选项卡里能看到,但信息量大了会刷屏。建议把关键调试信息用FileWrite()写入到自定义的CSV文件里,配合时间戳逐条记录,排查起来更清晰。
5. 一些经验之谈和后续扩展
这个项目我做下来最深的感受是:跟单EA的逻辑主体其实不难,难的是把各种异常情况都想到并且处理掉。网络断线、重启、品种不存在、手数拒单、滑点补偿……任何一环没考虑到,实盘的时候都会给你上一课。
对于刚开始做跟单EA的朋友,我建议分三步走:
第一步,先用文件方案打通主从两端的基本流程,摸清订单事件的生命周期。 第二步,换Socket方案,引入心跳和断线重连机制。 第三步,完善映射表、手数计算、全量持仓同步这些易错环节,再做模拟盘测试。
另外提一句,关于MT5跟单,MQL5的语言特性和MT4差别不小,尤其是订单机制从“订单-持仓”分离,跟单逻辑要重新设计。如果你本来就在MT4上验证好了交易逻辑,没必要急着迁移到MT5,先在一个平台跑稳定更重要。我想分享的实战经验就是:跟单EA不是做完就完事的,它更像一个需要持续维护的系统。行情稳定时安静如鸡,行情剧烈波动时才是真正检验它的时候。希望这篇内容能帮你少走点弯路。
本文还有配套的精品资源,点击获取