简介:MT4自动交易系统资源包面向外汇交易者、EA开发爱好者及初学量化策略的投资者,汇总了多套自动交易策略与配套学习材料,覆盖从策略逻辑理解、MQL4源码修改到MT4平台加载运行的完整链路。压缩包内共151个文件,主要包括53个mq4源码文件与51个ex4编译程序,便于对照学习或直接挂载到图表上运行;另外还提供了rar/zip打包的策略合集、doc文档说明、tpl图表模板、txt使用笔记等,辅助理解规则细节与风控思路。整体包体大小约16.99MB,目前已有998人学习下载。内容还包含海豚交易系统7.0交易规则、文字版对照录音,以及正确理解亏损、遵守纪律高于一切等交易理念文档,适合希望把自动执行与人工监督结合的中级交易者按需选用,也可作为编写自研EA的参考范例。
1. MT4自动交易EA合集:四个能直接编译挂载的策略源码入口
MT4自动交易里有个反直觉的现象:回测曲线最漂亮的EA,往往爆仓最快。尤其是网格马丁类,利润曲线看着像稳步爬坡,实际是在给一次大回撤攒风险。这篇文章要拆解的这套资源,是一份包含四个完整EA源码的合集包,涵盖均线交叉、网格马丁、通道突破、趋势跟踪这四类最常见的自动交易策略。每个EA都是独立的.mq4源码,带止损止盈、魔术编号和仓位控制,编译后拖到图表上就能跑。适合刚到MT4自动交易门口、想找一个现成EA跑通全流程的新手,也适合想拿现成代码改参数做二次开发的熟手。下面按我的拆包顺序:先看资源落地到家目录的完整路径,再逐段拆核心逻辑,然后讲回测参数,最后写常见坑和改造思路。
2. 目录结构与工作台准备:从压缩包到策略测试器的完整落地
拿到源码包先别急着双击,第一步是搞清里面每个文件是干什么的。MT4对文件目录有严格约定,放错文件夹,导航器里就找不到程序,这一步卡住的人不少。
2.1 资源包构成:.mq4源码、.set参数与.ex4程序的各自分工
资源解压后通常是这样的目录结构:
MT4_EA_Pack/ ├── MQL4/ │ └── Experts/ │ ├── MACrossEA.mq4 │ ├── GridMartinEA.mq4 │ ├── BreakoutEA.mq4 │ └── TrendFollowEA.mq4 ├── Presets/ │ ├── MACrossEA_Default.set │ ├── GridMartinEA_Conservative.set │ └── BreakoutEA_H4.set └── 说明文档.txt这个结构里,.mq4是MQL4语言的源代码,用MetaEditor打开后按F7即可编译。.ex4是编译后的可执行程序,正常OBJ存在于MQL4/Experts目录,图表导航器左侧的“智能交易系统”列表就是扫描这个目录显示的。.set是参数预设文件,回测时在策略测试器“参数”选项卡里一键加载,也可以在挂载EA到图表后右键加载。
我一般拿到EA源码的第一件事,就是打开MetaEditor重新编译一遍,而不是直接用包里带的.ex4。原因很简单:MT4版本更新频繁,旧编译产物经常出现“incompatible version”提示,自己本地编译保证了EA与当前平台版本匹配。编译成功后底部信息窗口会显示类似:
MACrossEA.mq4 MACrossEA.ex4: 0 errors, 0 warnings出现这个结果,说明源码至少在语法层面是健康的。如果报错,进到第5章的排查部分看。
2.2 MetaEditor环境准备:数据目录、编译选项与常用快捷键
把资源包放进MT4的正确姿势是打开数据目录。在MT4主界面按Ctrl+R是打开策略测试器,但打开数据目录的入口在“文件”菜单里,点击“打开数据文件夹”,会弹出一个资源管理器窗口,里面能看到MQL4目录、tester目录、config目录等。
然后把压缩包里的文件分别合并进去:
.mq4文件放进MQL4/Experts/;.set文件没有强制目录,放哪都行,加载时手动定位;.txt文档随意放置,不参与平台运行。
复制文件时如果MT4正在运行,建议先关闭MT4再复制,或者复制完成后在导航器窗口按F4刷新,否则资源管理器不会自动感知新文件。我踩过一次这个坑:文件复制进去了,导航器里怎么找都找不到,后来发现是MT4启动时缓存了目录列表,重启才好。
MetaEditor里常用的是F7编译、F4切回MT4主界面。编译输出窗口在下方,双击错误行会直接跳到对应代码位置。写EA时我习惯把#property strict放在第一行,这行声明让编译器做严格的类型检查,能提前暴露很多隐藏问题。
挂载EA到图表还有一个高频翻车点:自动交易总开关。MT4工具栏上有两个相关按钮,一个是“算法交易”总开关,一个在“选项→智能交易系统”里勾选“允许算法交易”。图表被加载EA后,右上角会显示一个笑脸图标,灰色笑脸表示自动交易被禁用,黄色笑脸才是运行中。很多新手把EA拖上图表就不管了,看到灰色的笑脸还以为EA坏了,其实是总开关没打开。
3. 核心模块拆解:均线交叉EA的完整交易链路
这个EA是整套资源里最适合逐行精读的一个。逻辑不复杂,但开仓、平仓、仓位管理、订单隔离这些完整链路都在,读懂它,另外三个EA只是换策略信号而已。
3.1 初始化与主循环:双均线信号怎么进入状态机
先看整体骨架,然后是开仓信号的核心判断:
#property strict input double LotSize = 0.01; input int FastMAPeriod = 5; input int SlowMAPeriod = 20; input int SL_Pips = 30; input int TP_Pips = 60; input int MagicNumber = 202401; datetime lastBarTime = 0; int OnInit() { return INIT_SUCCEEDED; } void OnTick() { if(Time[0] == lastBarTime) return; lastBarTime = Time[0]; double fastNow = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0); double fastPrev = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1); double slowNow = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0); double slowPrev = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1); if(fastPrev <= slowPrev && fastNow > slowNow) OpenOrder(OP_BUY); else if(fastPrev >= slowPrev && fastNow < slowNow) OpenOrder(OP_SELL); }Time[0]是当前正在形成的K线开盘时间戳,lastBarTime用来记录上一次处理过的K线时间。这段代码的核心作用是把策略从“每个tick都判断”降频成“每根新K线判断一次”。这样做最直接的好处是避免同一根K线内反复触发同一个信号,导致在同一个价位附近开出好几笔方向相同的单子。
iMA这里取了四条数据:快线当前值和前一根K线的值、慢线当前值和前一根K线的值。金叉的定义就是快线从慢线下方跑到上方,也就是fastPrev <= slowPrev && fastNow > slowNow,死叉反过来。PERIOD_CURRENT表示跟随当前图表周期,挂到H1图上就按H1计算,挂到M15就按M15,这个设计能让同一个EA在不同周期上复用。
3.2 开仓函数:手数换算、止损止盈与魔术编号隔离
信号触发后进入开仓函数,这个函数是每次写EA都会用到的模板:
void OpenOrder(int cmdType) { int count = CountMyTrades(); if(count > 0) return; double point = _Point; if(_Digits == 3 || _Digits == 5) point = _Point * 10; double price = (cmdType == OP_BUY) ? Ask : Bid; double sl = 0, tp = 0; if(SL_Pips > 0) sl = (cmdType == OP_BUY) ? price - SL_Pips * point : price + SL_Pips * point; if(TP_Pips > 0) tp = (cmdType == OP_BUY) ? price + TP_Pips * point : price - TP_Pips * point; int ticket = OrderSend(_Symbol, cmdType, LotSize, price, 3, sl, tp, "MA Cross", MagicNumber, 0, clrGreen); if(ticket < 0) Print("开仓失败,错误码:" + IntegerToString(GetLastError())); else PrintFormat("开仓成功,ticket=%d,价格=%f", ticket, price); }CountMyTrades这个函数的写法很典型,它倒序遍历整个订单列表,只统计当前图表、当前魔术编号下的订单:
int CountMyTrades() { int cnt = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if(OrderSymbol() == _Symbol && OrderMagicNumber() == MagicNumber) cnt++; } } return cnt; }魔术编号是EA自我保护的关键。一个账户里可以有多个EA在跑,甚至同图表挂两个策略,如果把OrderSelect选中后直接对订单操作,很可能会动到别的EA甚至手工单。通过OrderMagicNumber()和OrderSymbol()双重过滤,保证这个EA只管自己名下的单子。我在写多策略组合时,会给每个EA分配一个独立的MagicNumber区间,这样后续做统计和风控时一眼就能识别订单归属。
止损止盈这里有个单位换算问题:_Point在5位报价下是0.00001,而传统意义上的1 pip在5位报价下是10点,所以要用_Point * 10来做换算。如果参数里SL_Pips设成30,实际价格距离就是30个pipette。不同平台的点值定义不一样,这个换算逻辑要配合自己平台做调整。
3.3 平仓与移动止损:尾盘离场、浮盈保护
开仓只是开始,资金曲线好不好看,更取决于平仓管理。这个EA的平仓函数做了两件事:移动止损和尾盘清仓:
void ManagePosition() { for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderMagicNumber() != MagicNumber || OrderSymbol() != _Symbol) continue; if(MoveSL_Pips > 0) { double newSL = 0; if(OrderType() == OP_BUY) newSL = Bid - MoveSL_Pips * point; else newSL = Ask + MoveSL_Pips * point; if((OrderType() == OP_BUY && newSL > OrderStopLoss()) || (OrderType() == OP_SELL && newSL < OrderStopLoss())) { OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(newSL, _Digits), OrderTakeProfit(), 0, clrRed); } } if(CloseBeforeBarClose_Minutes > 0) { datetime barClose = Time[0] + PeriodSeconds(); if(TimeCurrent() >= barClose - CloseBeforeBarClose_Minutes * 60) CloseOrder(OrderTicket()); } } }移动止损的逻辑关键在于只往盈利方向推。newSL > OrderStopLoss()买单调用来判断新止损价是否高于当前止损价,只有浮盈推进时才调用OrderModify,否则频繁修改止损既增加系统调用开销,也可能触发平台限制。OrderModify前必须用NormalizeDouble(newSL, _Digits)对齐精度,否则个别平台会返回“invalid stops”错误。
尾盘清仓是一种典型的日内交易纪律:参数CloseBeforeBarClose_Minutes设为30,就是提前30分钟把当根K线的持仓全部平掉,避免隔夜跳空和突发事件。这个逻辑对新手特别友好,它强制策略在固定的时间段内结束交易,不会留下一堆隔夜单等第二天开盘被甩。
3.4 点差保护与手工单隔离:防止买在最高点
EA还有一个容易被忽视的细节——点差过滤。在数据公布时段,点差会突然拉大,此时信号可能刚好触发,如果照常开仓,等于买在明显不利的价位:
input int MaxSpreadPips = 30; int spreadPips = (int)((Ask - Bid) / point); if(spreadPips > MaxSpreadPips) { Print("当前点差过大,跳过开仓"); return; }Ask - Bid就是即时点差,把它除以point得到以点表示的值。当超过参数阈值时直接跳过本次开仓,这就是把“不做这笔交易”当成一种有效风控手段。马丁EA尤其需要这个保护:它总在亏损后加仓,如果加仓时点差过大,等于在额外的成本上方继续放大风险敞口。
4. 参数回测与实盘挂载:网格EA与突破EA的验证流程
均线交叉EA看懂后,剩下三个EA的核心逻辑快速过一遍,重点讲回测怎么做才可信。
4.1 网格马丁EA:加仓间距、倍数与熔断参数
网格马丁EA的策略思路是:价格每下跌一档买入一单,每上涨一档卖出一单,持仓数量越多,单笔手数越大,整体浮亏靠最后一批单子回本。核心加仓逻辑如下:
input double StartLot = 0.01; input double LotMultiplier = 1.5; input int GridStepPips = 50; input int MaxTrades = 6; input int MaxDrawdownPercent = 30; void OnTick() { if(nextBuyPrice == 0) { nextBuyPrice = Ask - GridStepPips * point; nextSellPrice = Bid + GridStepPips * point; } if(Bid <= nextBuyPrice) { int cnt = CountGridTrades(); if(cnt < MaxTrades) { double lot = NormalizeDouble(StartLot * MathPow(LotMultiplier, cnt), 2); OrderSend(_Symbol, OP_BUY, lot, Ask, 3, 0, 0, "Grid", MagicNumber, 0, clrBlue); nextBuyPrice = Ask - GridStepPips * point; } } if(Ask >= nextSellPrice) { int cnt = CountGridTrades(); if(cnt < MaxTrades) { double lot = NormalizeDouble(StartLot * MathPow(LotMultiplier, cnt), 2); OrderSend(_Symbol, OP_SELL, lot, Bid, 3, 0, 0, "Grid", MagicNumber, 0, clrRed); nextSellPrice = Bid + GridStepPips * point; } } }这里MathPow(LotMultiplier, cnt)计算加仓后的手数:第一单0.01,第二单0.015,第三单0.0225,呈几何级数增长。MaxTrades是总单数上限,这是网格马丁EA最关键的熔断参数,没有它,遇到单边趋势会一直加仓到爆仓。
浮亏熔断是网格马丁的“后悔药”,当全部网格单的总浮亏超过账户余额一定比例时,强制清仓:
double globalProfit = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if(OrderSymbol() == _Symbol && OrderMagicNumber() == MagicNumber) globalProfit += OrderProfit() + OrderSwap(); } } if(globalProfit < 0) { double lossPercent = MathAbs(globalProfit) / AccountBalance() * 100; if(lossPercent >= MaxDrawdownPercent) { CloseAllGridOrders(); Print("触发熔断,全部网格单已清仓"); } }我在跑这个EA的回测时,会把GridStepPips分别设成30、50、80,把MaxTrades设成4、6、8,交叉对比。很多人只看总利润曲线,却忽略了一个致命指标——最大回撤。网格马丁EA的利润和回撤几乎成正比,参数越激进,回测曲线越好看,实盘爆仓风险越高。这个EA最好配合单一品种,不要跨品种同时跑多个网格,否则账户总仓位会瞬间失控。
4.2 突破EA:通道识别与订单执行
突破EA的策略逻辑是:统计过去N根K线的最高价和最低价形成一个箱体通道,价格向上突破箱体上沿就做多,向下跌破箱体下沿就做空。代码比网格EA简洁:
input int ChannelPeriod = 24; input int BreakoutPips = 10; input int SL_Pips = 40; input int TP_Pips = 80; double upLevel = iHigh(_Symbol, PERIOD_H1, iHighest(_Symbol, PERIOD_H1, MODE_HIGH, ChannelPeriod, 1)); double dnLevel = iLow(_Symbol, PERIOD_H1, iLowest(_Symbol, PERIOD_H1, MODE_LOW, ChannelPeriod, 1)); if(Bid > upLevel + BreakoutPips * point) OrderSend(_Symbol, OP_BUY, LotSize, Ask, 3, Ask - SL_Pips * point, Ask + TP_Pips * point, "Break", MagicNumber, 0, clrGreen); if(Ask < dnLevel - BreakoutPips * point) OrderSend(_Symbol, OP_SELL, LotSize, Bid, 3, Bid + SL_Pips * point, Bid - TP_Pips * point, "Break", MagicNumber, 0, clrRed);iHighest返回过去24根K线中最高价的位置索引,把这个索引再传给iHigh取出具体价格值。BreakoutPips是过滤假突破的偏移量:价格必须突破通道上沿并超过10点才触发开仓。这样做的目的是减少在通道边缘反复震荡时被反复打脸的次数。
突破EA的回测参数重点在ChannelPeriod。周期太小,通道窄,触发频繁,手续费和止损成本会吃掉利润;周期太大,通道宽,信号少,一年可能就几笔交易。我一般用H1配合24周期做基准,再用48和72周期做对比,观察交易次数和净利润之间的平衡点。
4.3 策略测试器跑出可信结果的参数模板
回测不是简单点“开始”就完事,参数设置直接影响结论是否可信。我给这套资源跑回测时的固定模板如下:
| 配置项 | 推荐设置 | 原因 |
|---|---|---|
| 交易品种与周期 | EURUSD H1 | 主流品种流动性好,历史数据完整 |
| 回测区间 | 至少2年 | 覆盖趋势和震荡行情 |
| 建模方式 | 每个tick基于真实tick | 最接近实盘成交路径 |
| 点差设置 | 按当前交易商默认加点差 | 忽略了点差会让回测虚高 |
| 参数加载 | 加载对应的.set文件 | 保证和实盘挂载参数一致 |
在策略测试器里加载.set文件的方式是:选好EA后,右侧“参数”选项卡底部有一个“加载”按钮,点击后定位到Presets目录即可。加载后参数表会变成预设值,再点“开始”跑回测。跑完主要看四个指标:净利润、最大回撤、盈利因子、总交易笔数。
我特别留意总交易笔数,如果一年只有不到20笔,样本量太小,统计意义有限;如果笔数很大但盈利因子只略高于1,说明策略靠堆交易次数赚小钱,手续费一上调可能就转为亏损。回测报告只能帮你排除明显不合理的策略,不能用来断言未来收益,这一点一定记牢。
5. 常见问题排查:编译报错、回测失真与订单不成交的实例记录
这套资源挂载过程中有几个高频问题,我按实测踩坑记录的形式整理出来,每一条都按现象、原因、解决三步写清楚。
5.1 编译不过:历史函数与新版MQL4的兼容差异
现象:把.mq4文件拖进MetaEditor按F7,下方错误窗口弹出一片红色,最常见的报错是'OrderSend' - wrong parameters count和'iMA' - parameter conversion not allowed。
原因:新版MQL4编译器默认开启严格模式,要求函数参数数量和类型必须完全匹配。早期写的EA代码没有#property strict声明,依赖编译器自动转换类型,在新版本下全部暴露成了错误。另外Print里直接用+拼接数字和字符串在新版里也被禁止,必须用类型转换函数。
解决:在源码第一行加上#property strict。然后逐个错误行修正,整数参数用(int)显式强转,打印字符串用IntegerToString(GetLastError())替换直接拼接。这类报错不是程序逻辑问题,纯粹是语法规范和版本兼容,一般几分钟能修完。
5.2 回测失真:tick模式、点差与历史数据质量
现象:同一个EA,用“每根K线”模式回测年化收益翻倍,换成“每个tick基于真实tick”后利润缩水一半;在某交易商平台的历史数据上回测很漂亮,换一家交易商数据后曲线完全变形。
原因:每根K线模式下,系统假设订单只在K线收盘价成交,忽略了K线运行过程中最高价、最低价对止损止盈的触发路径。点差设置也是一样,默认的0点差回测会把每次开仓成本节省下来,累积起来就是一笔虚拟利润。
解决:回测建模方式一律选“每个tick基于真实tick”,点差按实盘默认值加上去。如果平台没有该品种的详细tick数据,在历史数据中心先下载完整数据再回测。跑实盘前还要用可视化回测模式播放一遍,确认订单开平仓位置和信号标注一致。
5.3 订单不成交:止损止盈距离、挂单距离与算法交易开关
现象:EA日志里打印invalid stops或直接没有报错,但图表右上角EA图标是灰色的,单子始终不开。
原因:invalid stops通常是止损价距离现价太近,不满足交易商的最小止损距离要求;灰色图标是自动交易总开关没打开;还有一种情况是前一次OrderSend还没有返回结果,下一次OrderSend已经触发,系统返回trade context busy。
解决:止损参数先按平台规定的最小止损距离设置,比如某平台要求止损至少距现价20点,那么SL_Pips低于20就会被拒单。挂载EA前确认工具栏“算法交易”按钮已点亮。代码层面在OrderSend前加while(IsTradeContextBusy()) Sleep(50);做等待,并在下单后加少量Sleep(100),给平台时间完成订单上下文切换。
5.4 回测最佳参数实盘翻车:参数过拟合的自查点
现象:用2019到2021年的数据优化出一组参数,实盘跑了三个月开始连续亏损,而同一时间段的回测曲线依然漂亮。
原因:这是一类典型过拟合问题。EA的input参数越多,组合空间越大,优化器越容易找到一条只在历史数据上成立的参数组合。真实施加行情里价格路径不会完全重复,过去最赚钱的参数往往也是对未来最敏感的参数。
解决:优化完参数后,用样本外数据做验证——假设用2019-2021优化,就保留2022-2023的数据不参与优化,直接拿优化后的参数去跑这一段。如果两段数据表现差距过大,说明参数过拟合。排查时还看两个点:策略中的逻辑是否依赖特定品种的波动特性,以及交易笔数是否足够支撑结论。我自己的经验是,把参数数量控制在6个以内,同时只优化最核心的三四个,能显著降低过拟合概率。
6. 二次开发:把单品种EA改造成多品种轮询的风控管理器
最后给一个具体的改造方向:把单图表EA改成一份代码同时监控多个品种,用一个图表管理整个账户的自动交易。
核心思路是抽离品种相关的部分。原代码里大量使用_Symbol、PERIOD_CURRENT,改造后全部换成参数传入。我在OnInit里用StringSplit解析逗号分隔的品种列表,再为每个品种分配独立的MagicNumber段:
input string TradeSymbols = "EURUSD,GBPUSD,XAUUSD"; input int BaseMagic = 202410; input double MaxTotalLot = 0.5; string Symbols[]; int Magics[]; int OnInit() { string parts[]; int n = StringSplit(TradeSymbols, ',', parts); ArrayResize(Symbols, n); ArrayResize(Magics, n); for(int i = 0; i < n; i++) { Symbols[i] = parts[i]; Magics[i] = BaseMagic + i; } return INIT_SUCCEEDED; } void OnTick() { for(int i = 0; i < ArraySize(Symbols); i++) { string sym = Symbols[i]; if(TotalOpenLot() > MaxTotalLot) continue; CheckSignal(sym, Magics[i]); } }CheckSignal就是原来均线交叉EA里的开仓判断,只是把_Symbol替换成了sym参数,把MagicNumber替换成Magics[i]。TotalOpenLot统计账户所有订单的总手数,超过阈值就跳过新开仓,这是多品种版最需要的全局风控:原先每个EA只管自己,多个EA同时开仓时账户总体仓位可能失控。
改造后的验证流程,我每次必走四步:先用原来的单品种参数跑一遍回测作为基线,记录净利润和最大回撤;然后跑多品种版本,对比总手数曲线是否超出预期;接着用可视化模式观察一遍订单序列,确认多个品种之间的下单顺序和风控逻辑没有互相干扰;最后在小仓位模拟盘挂一周,观察真实点差下执行是否正常。从那以后我每次改完EA,都强制走一遍这四步,缺一步都不上实盘,这套流程帮我避掉过好几回改造引入莫名其妙的亏损源。希望帮到你。
本文还有配套的精品资源,点击获取