简介:面向MT4平台的外汇自动交易学习与应用,这套资源整合了多个自动交易系统(EA)及其配套文档,适合希望借助程序化策略提升交易效率、降低情绪干扰的初学到进阶交易者。压缩包共151个文件,包含53个mq4策略源码、51个ex4可执行文件,以及tpl模板、doc说明、rar打包脚本与txt配置等,整体约16.99MB,便于在MetaTrader 4中直接导入、测试与修改。已有998人下载学习,可参考他人使用反馈。资源中既有海豚交易系统等成熟策略的规则讲解与录音对照,也有正确理解亏损、交易损失提示等风控与纪律文档,并附有多个EA源码与编译版本,方便用户理解逻辑、回测优化或直接挂载运行。通过这套资料,读者可以掌握EA的编写、安装、监控与参数调整思路,同时建立对自动交易风险的正确认知,为实盘应用打下基础。
1. MT4 自动交易系统:一个 EA 包拆开之后的真实骨架
把 EA 拖进 MT4 图表、点亮自动交易按钮,账户就开始自己开仓平仓——这个画面听着很爽,但真正上手的人都知道,网上能找到的 EA 十个里有八个要么不开单、要么一开单就亏,剩下两个还是加密过的黑匣子,想改一行参数都无从下手。这套资源包是某开发者长期跑过的 MT4 自动交易系统合集,包含六个可直接编译的 EA 源码和配套说明,覆盖趋势跟踪、网格、突破、马丁格尔四类主流策略。对想研究 EA 自动交易、需要一套「能跑起来还能二次开发」模板的人来说,它比那种下载下来只有 ex4 文件的资源靠谱得多。
2. 项目构成与技术栈:MQL4 文件体系、订单生命周期和六个策略的分工
2.1 MQL4 编程模型:EA 的四个入口函数
MQL4 是 MT4 平台内置的类 C 语言,EA 本质上是一个事件驱动脚本,平台的行情推送会触发对应的回调函数。新版 build 600 之后的 MT4 全部采用事件函数模型,核心入口有四个:OnInit()负责参数初始化和资源申请,OnDeinit()负责卸载时清理指标句柄、定时器等资源,OnTick()是策略主函数、每个报价进来都会触发一次,OnTimer()则适合那些不依赖报价、按固定时间间隔运行的系统。
一个最精简的 EA 骨架长这样:
#property strict input double RiskPercent = 1.0; // 每笔交易占用账户净值的百分比 input int MagicNumber = 202401; // 订单标识,用来区分是不是本EA下的单 int OnInit() { // 在这里加载指标句柄、读取外部文件 return INIT_SUCCEEDED; } void OnDeinit(const int reason) { // 释放 IndicatorBuffers、删除定时器 } void OnTick() { // 每个新报价进来都会触发一次 }注意#property strict这一行。旧版 MQL4 允许隐式声明变量,加上 strict 之后所有变量必须显式声明,编译器会帮你抓出很多手误。参数区用input关键字声明,这样 EA 拖到图表上时参数会出现在输入框里,不用改代码就能调。
MagicNumber 是整个订单管理体系里最关键的数字。一个账户可能同时挂三个不同品种的 EA,平台不会帮你区分订单是谁创建的,只能靠 MagicNumber 过滤。后面所有订单统计函数都会带上这个参数,它相当于每个 EA 实例的身份证号。
2.2 六个 EA 的文件清单与策略分工
这套资源包不是单一策略,而是一个策略组合。拆开目录后你会看到以下文件:
| 文件 | 策略类型 | 适用行情 | 风险等级 |
|---|---|---|---|
| TrendFollowEA.mq4 | 双均线趋势跟踪 | 趋势明朗的单边行情 | 低 |
| GridEA.mq4 | 网格交易 | 震荡行情 | 中 |
| BreakoutEA.mq4 | 唐奇安通道突破 | 趋势启动初期 | 中高 |
| MartingaleEA.mq4 | 马丁格尔加仓 | 震荡行情 | 高 |
| CommonLib.mqh | 公共函数库 | 订单管理 / 资金管理 | — |
| ReadMe.txt | 使用说明与参数表 | — | — |
四个策略的定位差异很明确。TrendFollowEA 是主力,适合挂在欧美这种波动相对平滑的主流品种上慢慢跑;GridEA 和 MartingaleEA 都是赚震荡的钱,但一个靠固定间距挂单、一个靠逆势加仓摊薄成本,两者风险模型完全不同;BreakoutEA 赌的是「横盘结束的那一下」,开仓频率低但单笔利润预期高。
设计上这六套 EA 共享同一个CommonLib.mqh,里面集中了订单统计、手数计算、日志输出这类公共逻辑。二次开发时改公共库,所有 EA 同时生效,这也是我在源码里优先看CommonLib.mqh的原因。
2.3 订单统计:倒序遍历与双重过滤
公共库里第一个值得看的函数是订单统计。所有 EA 在下单前都要先问一句「我是不是已经持有仓位了」,这个问题看起来简单,但实现上有几个细节容易翻车:
int CountOpenOrders(int magic) { int count = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() == Symbol() && OrderMagicNumber() == magic) count++; } return count; }第一,循环必须倒着来。OrdersTotal()返回当前账户所有挂单和持仓的总数,如果在循环体内部平仓或删单,订单集合的长度会变化,正序遍历会漏单。第二,OrderSelect的第三个参数MODE_TRADES表示只扫描已成交的持仓单,挂单属于MODE_PENDING,这是两个集合。第三,OrderSymbol() == Symbol()这句过滤很关键——一个 EA 挂在 EURUSD 图表上,不应该去数 GBPUSD 的订单数,否则两个品种各挂一个 EA 就会互相干扰。
MagicNumber 的过滤是第二道保险。即使你把同一个 EA 分别挂在 EURUSD 和 GBPUSD 两个图表上,只要两边的 MagicNumber 设置成不同值,各自统计到的订单就是独立的。
2.4 架构上的一个设计取舍:参数集中到 input 区
我拆这套 EA 时注意到一个习惯:所有可调参数全部用input暴露出来,代码里几乎看不到硬编码的数字。以 TrendFollowEA 为例,输入参数区是这样的:
input int FastMAPeriod = 10; // 快线均线周期 input int SlowMAPeriod = 30; // 慢线均线周期 input int InitialStopPips = 250; // 初始止损点数 input int TakeProfitPips = 500; // 初始止盈点数 input int UseTrailingStopPips = 150; // 移动止损触发距离 input double RiskPercent = 1.0; // 风险比例 input int MagicNumber = 202401; // 订单标识这个设计直接决定了后续调优的体验。回测优化器需要反复修改参数组合,如果策略逻辑里混了硬编码数值,优化器根本碰不到那个变量。把参数全部提到 input 区,策略测试器可以在几万组参数组合里自动搜索最优解,不需要一行一行改代码。这算是一个架构层面的成熟标志。
3. 核心代码逐段拆解:趋势 EA 从信号判断到移动止损的完整链路
3.1 信号生成:双均线交叉用「收盘确认」写法
TrendFollowEA 下单逻辑的第一步是判断方向。双均线交叉是最简单的趋势信号——快线上穿慢线做多,快线下穿慢线做空。问题是怎么判断「上穿」才算数。很多新手 EA 直接对比当前 K 线的两条均线值,只要快线在慢线上方就开多,结果一根 K 线内部均线可能反复交叉好几次,单量瞬间翻倍。
这套 EA 用的是收盘确认写法:
bool BuySignal() { // 用已收盘K线判断,不使用当前正在形成的K线 double fast1 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1); double slow1 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1); double fast2 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 2); double slow2 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 2); return (fast2 <= slow2 && fast1 > slow1); }iMA的参数顺序是:品种、周期、均线周期、位移、均线类型、价格类型、K线序号。最后这个 shift 参数是核心——1表示上一根已收盘 K 线,2表示再上一根。信号成立的条件是「第二根 K 线收盘时快线还在慢线下方,第一根 K 线收盘时快线已经上穿慢线」,也就是交叉发生在上一个已完成周期内。
这套写法的价值在于它把信号确定在了「某根 K 线收盘的那一刻」。回测和实盘用的是同一套数据,不会出现实盘中 K 线走了一半均线交叉、信号闪烁的问题。这是一个避免信号重绘的血泪经验。
3.2 开仓与止损:OrderSend 的参数顺序不能记错
信号确认后进入开仓流程。趋势 EA 通常在下单的同时就把初始止损和止盈带进去,避免「先开仓再改单」的中间状态:
void TryOpenTrade() { if(CountOpenOrders(MagicNumber) > 0) return; double lot = CalcLotSize(RiskPercent); if(BuySignal()) { double entry = Ask; double sl = entry - PipsToPrice(InitialStopPips); double tp = entry + PipsToPrice(TakeProfitPips); int ticket = OrderSend(Symbol(), OP_BUY, lot, entry, 3, sl, tp, "TrendFollowEA", MagicNumber, 0, clrGreen); if(ticket < 0) Print("开仓失败,错误码:", GetLastError()); } else if(SellSignal()) { double entry = Bid; double sl = entry + PipsToPrice(InitialStopPips); double tp = entry - PipsToPrice(TakeProfitPips); int ticket = OrderSend(Symbol(), OP_SELL, lot, entry, 3, sl, tp, "TrendFollowEA", MagicNumber, 0, clrRed); if(ticket < 0) Print("开仓失败,错误码:", GetLastError()); } }OrderSend的十一个参数按顺序是:品种、订单类型、手数、开仓价格、最大滑点、止损价、止盈价、订单注释、MagicNumber、过期时间、箭头颜色。新手最容易栽在「开仓价格」上——做多要用Ask,做空要用Bid,方向反了订单会被直接拒掉。
滑点参数填的是允许的价格偏差点数,回测时可以填 0,实盘建议填 3 到 5,太小会频繁出现 requote 导致订单提交失败。止损止盈的计算逻辑是:做多时止损在入场价下方、止盈在入场价上方;做空反过来。代码里PipsToPrice负责把「点数」换算成「价格差」,因为不同品种的小数位不一样,后面会单独讲。
开仓失败时Print输出错误码,这一步不是为了美观,是为了在 MT4 的「专家」标签页里快速定位问题。GetLastError()返回 130 表示止损距离过近,返回 138 表示重新报价,返回 146 表示交易繁忙,常见错误码我后面避坑章节会再说。
3.3 移动止损:用已收盘 K 线跟踪利润
固定止盈只能拿住一小段利润,趋势行情里最忌讳提前下车。这套 EA 的移动止损模块负责在行情朝有利方向走时不断抬高止损线,把浮动利润锁住:
void TrailingStop() { double dist = PipsToPrice(UseTrailingStopPips); for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue; if(OrderType() == OP_BUY) { double newSL = High[1] - dist; double oldSL = OrderStopLoss(); if(newSL > oldSL + _Point) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE); } else if(OrderType() == OP_SELL) { double newSL = Low[1] + dist; if(newSL < OrderStopLoss() - _Point || OrderStopLoss() == 0) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE); } } }High[1]是上一根已收盘 K 线的最高价,Low[1]是最低价。用已收盘 K 线而不是当前实时价格,能避免盘中瞬间插针造成的虚高或虚低——价格拉一个长上影再回落,移动止损应该以收盘为锚点,而不是被影线骗一次。
修改止损前先判断newSL > oldSL + _Point,加一个最小变化量的判断。这个细节很重要:如果不检查就直接OrderModify,行情每跳动一次就会触发一次止损修改请求,不仅消耗平台 API 调用次数,某些经纪商还会对频繁修改订单收额外费用。
3.4 手数计算:以损定量的仓位管理
仓位管理直接决定账户能不能活过连续亏损。这套 EA 用的是「以损定量」模型——先确定这单我愿意亏多少钱,再根据止损距离反推手数:
double PipsToPrice(int pips) { int factor = (_Digits == 5 || _Digits == 3) ? 10 : 1; return pips * _Point * factor; } double CalcLotSize(double riskPercent) { double riskMoney = AccountBalance() * riskPercent / 100.0; double stopDist = PipsToPrice(InitialStopPips); double tickValue = MarketInfo(Symbol(), MODE_TICK_VALUE); double rawLot = riskMoney / (stopDist * tickValue); double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); double lot = MathFloor(rawLot / lotStep) * lotStep; return NormalizeDouble(MathMax(minLot, MathMin(maxLot, lot)), 2); }PipsToPrice里那个 factor 是很多新手算错的地方。5 位报价的品种_Point是 0.00001,一个标准点(pip)等于 10 个 point;4 位报价的品种_Point是 0.0001,一个标准点等于 1 个 point。如果不做这个换算,止损距离会差 10 倍,仓位计算也跟着全错。
CalcLotSize的核心逻辑是:账户余额 × 风险百分比 ÷(止损距离 × 每手每点价值)= 手数。MODE_TICK_VALUE表示每手一个 point 变动对应的货币价值,这个值由经纪商根据品种计算,不能拍脑袋写死。算出来的原始手数还要做三件事:向下取整到lotStep的整数倍、限制在MODE_MINLOT和MODE_MAXLOT之间、NormalizeDouble修正浮点误差。
这一系列代码组合起来,就是这套趋势 EA 的完整交易链路:收盘确认信号 → 以损定量算手数 → 带止损止盈开仓 → 收盘 K 线跟踪移动止损。理解这条链路,后面改任何参数都有抓手。
4. 部署与运行:把 EA 装进 MT4、回测验证到挂载图表的完整流程
4.1 文件放置与编译
拿到源码后第一步不是直接打开 MT4 拖文件,而是先找到数据目录。MT4 的安装目录和可写数据目录可能是分离的,最稳妥的方法是打开 MT4 客户端,菜单栏「文件 → 打开数据文件夹」,这才是当前账户真正使用的目录。
打开后按路径放置文件:
# 目录结构 MQL4/ ├── Experts/ # 放 .mq4 源码和 .ex4 编译产物 │ ├── TrendFollowEA.mq4 │ ├── GridEA.mq4 │ ├── BreakoutEA.mq4 │ └── MartingaleEA.mq4 └── Include/ # 放公共头文件 └── CommonLib.mqh放置完用 MetaEditor 打开 .mq4 文件,按 F7 或点工具栏上的「编译」按钮。编译成功会在同一目录生成同名 .ex4 文件,底部信息栏显示「0 errors, 0 warnings」。如果报错,先看是不是CommonLib.mqh没放进 Include 目录——这个头文件找不到是最常见的编译错误来源。
这里有个值得强化的习惯:永远保留源码而不是直接用下载来的 .ex4。MT4 每个 build 版本对 ex4 的兼容性要求很严格,旧版编译的 ex4 在新版客户端会被拒载。手上有 .mq4 源码,换电脑、换平台、升级客户端之后自己重新编译一次就能继续用,这是「源码在手、心里不慌」的道理。
4.2 挂载到图表并开启自动交易
编译通过后回到 MT4,左侧「导航器」窗口展开「智能交易系统」,能看到刚才编译的 TrendFollowEA。把它直接拖到目标品种图表上,会弹出参数设置界面:
- 快线均线周期、慢线均线周期:默认 10 和 30,如果做 H4 级别可以放大到 20 和 50
- 初始止损点数、初始止盈点数:按止损比例 1:2 设置
- 移动止损触发距离:建议不小于初始止损的 60%
- 风险比例:默认 1.0,意思是每单最多亏账户净值的 1%
- MagicNumber:每次挂载实例必须改成不同值
确认弹窗里有「允许实时自动交易」选项,勾选它。同时检查工具条上的「自动交易」按钮,必须显示为绿色图标。这两处任何一个没开启,EA 都不会真正下单。
挂载完成后,图表右上角显示一个笑脸图标表示 EA 正常运行,显示哭脸说明策略报错或禁止交易。此时打开「工具箱」窗口的「专家」标签页,能看到 EA 的初始化日志和每次开仓失败的错误信息,这里就是 EA 的黑匣子探针。
4.3 策略测试器回测:按策略类型选择报价模型
回测是验证 EA 能不能跑的第一步。在 MT4 里按 Ctrl+R 打开策略测试器,选择要测试的 EA、品种、周期、时间范围和初始资金,最关键的一步是设定「模型」类型:
| 模型类型 | 原理 | 适用场景 |
|---|---|---|
| 每个报价(Every tick) | 按历史逐笔报价回放 | 网格 / 马丁等高频加仓策略 |
| 1分钟OHLC | 用1分钟K线的四个价格模拟 | 趋势跟踪类中低频策略 |
| 控制点(Control Points) | 只按生成的点位执行 | 快速粗筛,结果偏差大 |
趋势 EA 用「1分钟OHLC」大体够用,但网格 EA 和马丁 EA 必须用「每个报价」模式。后者的开仓逻辑对价格路径极其敏感——先插针还是先震荡,直接决定加仓次数和浮亏深度,用 1 分钟 OHLC 会严重低估风险。
回测跑完看报告时,不要只盯着净利润。重点看三个指标:获利因子(Profit Factor)最好大于 1.5、最大回撤(Max Drawdown)不能超过账户的 30%、恢复因子(Recovery Factor)越高越好。如果净利润很高但回撤也很大,这个 EA 离爆仓只差一次连续亏损。
回测报告还有一个容易被忽略的细节:交易次数。一个策略如果回测五年只开了十几次仓,样本量太小,统计上没有说服力。趋势 EA 尤其容易这样,遇到这种情况可以适度缩小均线周期或放宽信号滤波条件,让交易频率提高到至少一个月 4 到 5 次,再重新评估结果。
4.4 参数优化的边界:遗传算法不是万能钥匙
策略测试器的「优化」功能可以自动搜索最优参数组合。勾选优化、设定每个参数的起止范围和步进,MT4 会穷举或使用遗传算法跑几千组组合。这个功能好用,但也最容易把人带进过拟合的坑。
我见过太多人把回测里的最优参数组合直接搬上实盘,结果实盘表现和回测差一大截。原因是优化器搜索的是「历史数据上表现最好」的参数,但未来行情不会完全复刻历史。一个负责任的优化流程是这样的:先把数据分成两段,前 70% 用来优化找参数,后 30% 用来验证参数,如果验证段的表现跟优化段差距不大,才说明参数有泛化能力。这套流程叫 out-of-sample 验证,在 MT4 里就是跑两次回测、换不同的起始时间而已。
5. 实战踩坑排查:回测盈利但实盘亏损的五个常见原因
5.1 回测曲线很漂亮,一上实盘就稳定亏损
现象是策略测试器里跑出漂亮的资金曲线,年化回报 40%,最大回撤不到 15%,结果挂到真实小账户上跑了一个月,稳定亏损,连回测十分之一的水平都达不到。
问题通常出在三个环节。第一是信号用了未来函数,比如用当前正在形成的 K 线去判断均线交叉,回测时这条 K 线的最终收盘数据已经存在了,程序「偷看」了未来数据。第二是回测模型太粗糙,用了「仅开盘价」或「1分钟OHLC」去跑一个对价格路径高度敏感的策略。第三是回测里没计入点差和滑点,每次开仓平仓都少算几美元,一百笔交易下来差距就出来了。
解决方法是回头逐项排查。信号计算里所有 K 线序号必须大于等于 1,绝不使用当前未收盘 K 线;回测模型切到「每个报价」模式,点差设置为当前品种的常见水平(EURUSD 通常在 10 到 15 个 point);同一参数在细模式下重跑一次,如果结果和粗模式差异超过 30%,说明策略对报价噪声太敏感,需要加滤波条件。
5.2 EA 拖到图表上没有任何反应
现象是图表右上角没有笑脸图标,或者图标有了但「专家」标签页里没有任何输出日志,EA 像一块石头。
原因就那么几个。最常见的 .mq4 没编译、导航器里显示的是源码文件而不是可执行 EA;其次是工具条上的「自动交易」按钮是红色禁用状态;还有一种情况是参数里把 MagicNumber 设成了跟已有持仓一样的值,导致CountOpenOrders返回数量大于 0,开仓条件被自己堵死。
解决流程:先在 MetaEditor 里按 F7 确认编译生成 .ex4;再确认自动交易按钮是绿色;最后打开「专家」标签页看有没有报错日志。一个合格 EA 的OnInit()里应该至少输出一行参数摘要,比如用Print("TrendFollowEA loaded, SlowMA=", SlowMAPeriod),连初始化日志都没有的 EA,调试起来等于蒙着眼睛找路。
5.3 多图表挂载同一 EA 重复开仓
现象是同时把 TrendFollowEA 挂在 EURUSD 的 H1 和 M15 两张图表上,结果一个信号来了两个实例同时开仓,仓位直接翻倍。
根源是 MagicNumber 没区分。两个实例都用了默认的 202401,各有各的持仓统计,CountOpenOrders互相统计不到对方。另一个常见根源是信号本身没有做「单根 K 线只触发一次」的限制,即使只有一个图表,均线交叉信号在当前 K 线内反复满足条件,OnTick 每次报价都会触发一次开仓逻辑。
解决方法是双管齐下。第一,每个图表实例设置独立的 MagicNumber;第二,在 OnTick 里记录上一次开仓的 K 线时间,只有 K 线时间发生变化才重新评估信号。判断逻辑可以这样写:
datetime lastBarTime = 0; void OnTick() { if(lastBarTime == Time[0]) return; lastBarTime = Time[0]; // 后续策略逻辑 }5.4 换了一家经纪商,同一套 EA 表现完全不同
现象是这个 EA 在某经纪商回测表现良好,换另一家之后同样的品种、同样的参数,信号频繁错位,开仓点位完全对不上。
核心原因是不同经纪商的 MT4 服务器时区不一样。有的服务器用 GMT+2,有的用 GMT+3,K 线的开盘时间因此错开一两个小时。日线级别的差异还不明显,H1 以下的策略入场点会被明显影响。另一个问题是报价精度——5 位报价和 4 位报价的差价不同,_Point值不同,点位换算和止损距离全要重新校准。
解决思路是放弃对绝对时间的依赖,改用 K 线本身的结构来定义信号。止损距离这类参数也从固定点数改成动态计算,常见的做法是用 ATR 自适应。iATR取最近 N 根 K 线的平均波幅作为止损距离,价格精度和时区差异就不再影响核心逻辑了。
5.5 网格和马丁 EA 扛不过单边行情
现象是网格 EA 在震荡行情里稳定盈利,账户净值曲线平滑向上,结果遇上一次几百点的单边行情,连续加仓把净值和保证金一起打穿,直接爆仓。
这不是 EA 的 bug,而是策略模型本身的数学特性。网格和马丁的盈利逻辑建立在「价格最终会回到持仓均价附近」的假设上,震荡行情里这个假设成立,一旦进入单边趋势,每一层加仓都扩大浮亏,仓位越重、回本需要的反弹幅度越大,最终超出账户承受极限。
解决这个风险要从参数层面做硬限制。至少三个参数必须强制设置:最大加仓层数(MaxLevels)、最大总手数占净值比例(MaxTotalLotsPercent)、净值回撤保护阈值(EquityProtectionPercent)。我一般会把马丁 EA 的净值保护阈值设为 30%,一旦浮亏触及立即全部平仓,宁可亏损出局也不深套。回测时专门找历史长周期里最极端的单边行情,把回测起止时间对准那段走势,专门检验仓位模型能不能扛过去。扛不住就降加仓倍数或缩小间距,这是马丁类 EA 唯一的保命手段。
6. 二次开发技巧:从均线改成布林带,外加仓位保护改造
想把这套资源变成自己的东西,最简单的起点是改造 TrendFollowEA 的信号模块。均线交叉换成布林带突破只需要替换BuySignal()和SellSignal()两个函数,其他开仓、止损、手数计算全部复用。
原版用两条均线判断趋势方向,改成布林带后逻辑变成「价格突破上轨做多、突破下轨做空」:
bool BuySignal() { double upper = iBands(Symbol(), 0, 20, 2, 0, PRICE_CLOSE, MODE_UPPER, 1); double prevUpper = iBands(Symbol(), 0, 20, 2, 0, PRICE_CLOSE, MODE_UPPER, 2); // 上一根K线收盘价突破上轨,且再上一根还没突破 return (Close[1] > upper && Close[2] <= prevUpper); }iBands的参数依次是品种、周期、均线周期 20、标准差倍数 2、位移 0、价格类型、轨道类型、K线序号。MODE_UPPER取上轨,MODE_MAIN取中轨,MODE_LOWER取下轨。逻辑上要求Close[2] <= prevUpper,确保突破只发生在上一个已收盘周期内,延续了收盘确认的无未来函数纪律。
另一个值得加的改造是账户级净值保护。原版 EA 只控制单笔风险,但如果连续亏损,账户还是可能快速缩水。在OnTick()最前面加一段:
void OnTick() { double equity = AccountEquity(); double balance = AccountBalance(); if(balance > 0 && equity < balance * 0.3) { CloseAllOrders(MagicNumber); Print("净值回撤超过阈值,全部平仓保护"); return; } // 原有策略逻辑 }CloseAllOrders的写法和CountOpenOrders对称,遍历所有持仓、按 MagicNumber 过滤后逐个调OrderClose。用AccountEquity而不是只看浮亏,是因为保证金占用和点差变化也会影响实际权益。
改完一定要验证。我的习惯是先把原版和改版在同一个时间段、同一个测试参数下各跑一遍回测,对比获利因子和最大回撤,而不是只看净利润谁高。然后手动把回测模式切到「每个报价」,剔除掉信号闪烁带来的虚假收益。从那以后我每次改策略都强制走一遍这个流程,布林带版和均线版在趋势行情里各有优势,但至少我能说清楚区别在哪,而不是靠感觉拍板。希望帮到你。
本文还有配套的精品资源,点击获取