加载中…
  • 会员VIP
  • 认证
  • 标签云
  • 站点地图
  • 置顶文章
  • 下载

{{userData.name}}

个人中心
后台
{{item.count}}
{{textHint.loading}}
  • {{data.name}}({{data.count}}){{data.name}}
写文章
  • 首页
  • 文章
  • 精选专题
  • 指标EA下载精
  • 指标EA视频
  • 交易心得
当前位置:首页-文章-MT5编程方法,交易经验杂谈-正文

MT5 EA开发实战:从零开始构建一个趋势跟踪EA(附完整代码)

18小时前00880

# MT5 EA开发实战:从零开始构建一个趋势跟踪EA(附完整代码)

> ⚠️ 风险提示:本文仅用于技术交流和学习目的,不构成任何交易建议。EA交易存在风险,请充分测试后谨慎使用。外汇和差价合约交易可能导致您的资金损失,请确保您理解相关风险。

---

## 📌 前言

很多交易者都有一个"EA梦"——把自己的交易策略写成自动交易程序,让电脑24小时帮你赚钱。

但理想很丰满,现实很骨感。很多人刚开始学EA开发就被MQL5的语法和各种函数劝退了。要么就是写出来的EA回测看起来很美,实盘一用就亏。

作为一个做了多年EA定制的开发者,我可以很负责任地说:**写一个能跑的EA不难,但写一个能稳定盈利的EA很难。**

不过,难不等于不能学。今天这篇文章,我就带你从零开始构建一个完整的趋势跟踪EA。不求你看完就能写出盈利的EA,但求能帮你建立对EA开发的整体认知,少走一些弯路。

这篇文章的目标读者:
- 有一点MQL4/MQL5基础,但还没写过完整EA的新手
- 想了解EA开发流程的交易者
- 打算自己动手改EA的进阶用户

如果你完全零基础,可能需要先补一补MQL5的基础语法。

---

## 🎯 一、EA策略设计:先想清楚再动手

写EA最忌讳的就是上来就敲代码。一个合格的EA,在写第一行代码之前,必须把策略逻辑想清楚。

我们今天要做的这个趋势跟踪EA,策略逻辑很简单:

### 核心策略逻辑
1. **趋势判断:** 用200周期EMA判断大趋势
- 价格在EMA上方 → 只做多
- 价格在EMA下方 → 只做空
2. **入场信号:** 用MACD交叉作为入场信号
- 上升趋势中,MACD线上穿信号线 → 做多
- 下降趋势中,MACD线下穿信号线 → 做空
3. **止损设置:** 固定止损(比如30点)
4. **止盈设置:** 固定止盈(比如60点),或者用追踪止损
5. **仓位管理:** 固定手数,或者按资金百分比计算

### 为什么选这个策略?
- 逻辑清晰,容易理解和实现
- 趋势跟踪是最经典的策略类型之一
- 包含了EA开发中最常见的几个模块:
- 指标计算
- 趋势判断
- 信号生成
- 订单管理
- 风险管理

学会了这个,你就能在此基础上改出各种各样的趋势类EA。

---

## 📋 二、EA架构设计:搭好骨架再填肉

一个结构清晰的EA,应该分成几个独立的模块。这样代码易读、易改、易维护。

我们的EA将分为以下几个模块:

```
// 1. 输入参数模块
// 所有可以调整的参数都放这里
// 2. 全局变量模块
// 需要在不同函数间共享的变量
// 3. 初始化函数 OnInit()
// EA加载时执行一次
// 4. 主循环函数 OnTick()
// 每个tick执行一次,核心逻辑都在这里
// 5. 指标计算函数
// 计算EMA、MACD等指标
// 6. 信号判断函数
// 判断是否满足开仓/平仓条件
// 7. 订单操作函数
// 开仓、平仓、修改止损止盈
// 8. 资金管理函数
// 计算开仓手数
// 9. 去初始化函数 OnDeinit()
// EA卸载时执行一次
```

这是一个很标准的EA架构,大多数EA都可以套这个模板。

---

## 💻 三、代码实现:一步一步写

好了,开始写代码。我会分段讲解,最后给出完整代码。

### 1. 输入参数

这是EA最"用户友好"的部分——用户可以在MT5里直接调整这些参数,不用改代码。

```mql5
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input double LotSize = 0.1; // 交易手数
input int EMA_Period = 200; // EMA周期
input int MACD_FastEMA = 12; // MACD快线周期
input int MACD_SlowEMA = 26; // MACD慢线周期
input int MACD_Signal = 9; // MACD信号线周期
input int StopLoss = 30; // 止损(点数)
input int TakeProfit = 60; // 止盈(点数)
input bool UseTrailingStop = false; // 是否使用追踪止损
input int TrailingStop = 20; // 追踪止损距离(点数)
input int MagicNumber = 12345; // EA魔术码(区分不同EA的订单)
input int Slippage = 3; // 允许滑点(点数)
```

**要点提示:**
- 📝 MagicNumber非常重要,它是EA的"身份证"。每个EA用不同的魔术码,才不会互相干扰对方的订单。
- 📝 所有数值都要给一个合理的默认值,用户不修改也能直接用。
- 📝 参数注释要写清楚,方便用户理解每个参数是干嘛的。

---

### 2. 全局变量和初始化

```mql5
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
int emaHandle; // EMA指标句柄
int macdHandle; // MACD指标句柄
double emaBuffer[]; // EMA数值缓冲区
double macdBuffer[]; // MACD主线缓冲区
double signalBuffer[];// MACD信号线缓冲区

//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
// 创建EMA指标
emaHandle = iMA(_Symbol, _Period, EMA_Period, 0, MODE_EMA, PRICE_CLOSE);
if(emaHandle == INVALID_HANDLE)
{
Print("创建EMA指标失败,错误码:", GetLastError());
return(INIT_FAILED);
}

// 创建MACD指标
macdHandle = iMACD(_Symbol, _Period, MACD_FastEMA, MACD_SlowEMA,
MACD_Signal, PRICE_CLOSE);
if(macdHandle == INVALID_HANDLE)
{
Print("创建MACD指标失败,错误码:", GetLastError());
return(INIT_FAILED);
}

// 设置数组为时间序列(索引0是最新的bar)
ArraySetAsSeries(emaBuffer, true);
ArraySetAsSeries(macdBuffer, true);
ArraySetAsSeries(signalBuffer, true);

return(INIT_SUCCEEDED);
}
```

**要点提示:**
- 💡 在MQL5中,使用指标前需要先"创建"指标,获得一个句柄(handle)。
- 💡 `_Symbol`代表当前图表的品种,`_Period`代表当前周期。
- 💡 `ArraySetAsSeries`设置为true后,数组索引0就是最新的K线,索引1是前一根,以此类推。
- 💡 初始化失败一定要返回`INIT_FAILED`,这样MT5会显示初始化失败,不会乱开单。

---

### 3. 指标计算函数

每个tick我们都需要获取最新的指标数值。

```mql5
//+------------------------------------------------------------------+
//| 更新指标数据 |
//+------------------------------------------------------------------+
bool UpdateIndicators()
{
// 复制EMA数据
int copied = CopyBuffer(emaHandle, 0, 0, 3, emaBuffer);
if(copied < 3) { Print("复制EMA数据失败"); return false; } // 复制MACD主线数据 copied = CopyBuffer(macdHandle, 0, 0, 3, macdBuffer); if(copied < 3) { Print("复制MACD主线数据失败"); return false; } // 复制MACD信号线数据 copied = CopyBuffer(macdHandle, 1, 0, 3, signalBuffer); if(copied < 3) { Print("复制MACD信号线数据失败"); return false; } return true; } ``` **要点提示:** - ⚠️ `CopyBuffer`可能返回小于你请求的数量,尤其是在新K线形成的时候。一定要检查返回值。 - ⚠️ 我们只需要最近3根K线的数据就够了(当前K线+前两根),不用复制太多,浪费性能。 --- ### 4. 信号判断函数 这是EA的"大脑",决定什么时候开仓、什么时候平仓。 ```mql5 //+------------------------------------------------------------------+ //| 判断做多信号 | //+------------------------------------------------------------------+ bool IsBuySignal() { // 趋势判断:价格在EMA上方 bool isUptrend = (Close[1] > emaBuffer[1]);

// MACD金叉:前前根MACD线在信号线下,前一根MACD线上穿信号线
bool macdCrossUp = (macdBuffer[2] < signalBuffer[2]) && (macdBuffer[1] > signalBuffer[1]);

return isUptrend && macdCrossUp;
}

//+------------------------------------------------------------------+
//| 判断做空信号 |
//+------------------------------------------------------------------+
bool IsSellSignal()
{
// 趋势判断:价格在EMA下方
bool isDowntrend = (Close[1] < emaBuffer[1]); // MACD死叉:前前根MACD线在信号线上,前一根MACD线下穿信号线 bool macdCrossDown = (macdBuffer[2] > signalBuffer[2]) &&
(macdBuffer[1] < signalBuffer[1]); return isDowntrend && macdCrossDown; } ``` **要点提示:** - 🎯 注意我们用的是索引1(前一根K线)的数据来判断信号,而不是索引0(当前K线)。 - 🎯 为什么?因为当前K线还没走完,信号可能会反复变化。用已收盘的K线来判断,信号才是确定的。 - 🎯 这是新手最容易踩的坑之一——用当前K线判断信号,回测看起来很准,实盘根本赚不到钱。 --- ### 5. 订单操作函数 这部分是EA的"手",负责实际的买卖操作。 ```mql5 //+------------------------------------------------------------------+ //| 开多单 | //+------------------------------------------------------------------+ bool OpenBuy() { // 检查是否已有同方向订单 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) return false; double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double sl = price - StopLoss * _Point; double tp = price + TakeProfit * _Point; MqlTradeRequest request = {0}; MqlTradeResult result = {0}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = LotSize; request.type = ORDER_TYPE_BUY; request.price = price; request.sl = sl; request.tp = tp; request.deviation= Slippage; request.magic = MagicNumber; request.comment = "Trend Following EA"; if(!OrderSend(request, result)) { Print("开多单失败,错误码:", result.retcode); return false; } Print("开多单成功,订单号:", result.order); return true; } //+------------------------------------------------------------------+ //| 开空单 | //+------------------------------------------------------------------+ bool OpenSell() { // 检查是否已有同方向订单 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) return false; double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); double sl = price + StopLoss * _Point; double tp = price - TakeProfit * _Point; MqlTradeRequest request = {0}; MqlTradeResult result = {0}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = LotSize; request.type = ORDER_TYPE_SELL; request.price = price; request.sl = sl; request.tp = tp; request.deviation= Slippage; request.magic = MagicNumber; request.comment = "Trend Following EA"; if(!OrderSend(request, result)) { Print("开空单失败,错误码:", result.retcode); return false; } Print("开空单成功,订单号:", result.order); return true; } ``` **要点提示:** - ⚠️ MT5的订单系统和MT4不一样。MT5用`PositionSelect`检查持仓,用`TRADE_ACTION_DEAL`成交。 - ⚠️ 开仓前一定要检查是否已经有同方向的持仓,避免重复开仓。 - ⚠️ `_Point`是品种的最小价格变动单位,用它来计算点数,才能在不同品种上都正确运行。 - ⚠️ 一定要设置合理的滑点(deviation),否则行情波动大的时候可能开不了仓。 --- ### 6. 追踪止损函数 如果开启了追踪止损,我们需要在持仓盈利时不断上移止损。 ```mql5 //+------------------------------------------------------------------+ //| 更新追踪止损 | //+------------------------------------------------------------------+ void UpdateTrailingStop() { if(!UseTrailingStop) return; // 检查是否有持仓 if(!PositionSelect(_Symbol)) return; // 只处理本EA的持仓 if((int)PositionGetInteger(POSITION_MAGIC) != MagicNumber) return; double currentSL = PositionGetDouble(POSITION_SL); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { // 多单:价格上涨时上移止损 double newSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - TrailingStop * _Point; // 如果新止损比当前止损高,就更新 if(newSL > currentSL || currentSL == 0)
{
ModifySLTP(newSL, PositionGetDouble(POSITION_TP));
}
}
else // 空单
{
// 空单:价格下跌时下移止损
double newSL = SymbolInfoDouble(_Symbol, SYMBOL_ASK) +
TrailingStop * _Point;

// 如果新止损比当前止损低,就更新
if(newSL < currentSL || currentSL == 0) { ModifySLTP(newSL, PositionGetDouble(POSITION_TP)); } } } //+------------------------------------------------------------------+ //| 修改止损止盈 | //+------------------------------------------------------------------+ bool ModifySLTP(double sl, double tp) { MqlTradeRequest request = {0}; MqlTradeResult result = {0}; request.action = TRADE_ACTION_SLTP; request.symbol = _Symbol; request.sl = sl; request.tp = tp; request.magic = MagicNumber; if(!OrderSend(request, result)) { Print("修改止损止盈失败,错误码:", result.retcode); return false; } return true; } ``` **要点提示:** - 📈 追踪止损的逻辑:多单盈利时,止损跟着价格往上移;空单盈利时,止损跟着价格往下移。 - 📈 注意判断条件:只有新的止损比当前止损"更好"的时候才更新。多单是"更高",空单是"更低"。 - 📈 追踪止损不能代替止损本身,它只是在盈利的时候保护利润。 --- ### 7. 主循环 OnTick() 把所有模块串起来,每个tick执行一次。 ```mql5 //+------------------------------------------------------------------+ //| 主循环函数 | //+------------------------------------------------------------------+ void OnTick() { // 1. 更新指标数据 if(!UpdateIndicators()) return; // 2. 检查是否有新K线(只在新K线形成时判断信号,避免反复交易) static datetime lastBarTime = 0; if(Time[0] == lastBarTime) { // 同一根K线内,只更新追踪止损,不判断新信号 UpdateTrailingStop(); return; } lastBarTime = Time[0]; // 3. 更新追踪止损 UpdateTrailingStop(); // 4. 判断交易信号 if(IsBuySignal()) { // 有做多信号,先平掉空单(如果有的话),再开多单 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { ClosePosition(); } OpenBuy(); } else if(IsSellSignal()) { // 有做空信号,先平掉多单(如果有的话),再开空单 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { ClosePosition(); } OpenSell(); } } ``` **要点提示:** - ⏰ 这里的关键设计是"只在新K线时判断信号"。这样可以避免同一个信号反复触发,也避免了K线未收盘时信号不稳定的问题。 - ⏰ 同一根K线内,我们只更新追踪止损,不判断新信号。这是一个非常实用的设计。 - ⏰ `static`变量在函数调用之间会保留值,正好用来记录上一根K线的时间。 --- ### 8. 平仓函数和去初始化 ```mql5 //+------------------------------------------------------------------+ //| 平仓当前持仓 | //+------------------------------------------------------------------+ bool ClosePosition() { if(!PositionSelect(_Symbol)) return true; double price; ENUM_ORDER_TYPE orderType; if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { price = SymbolInfoDouble(_Symbol, SYMBOL_BID); orderType = ORDER_TYPE_SELL; } else { price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); orderType = ORDER_TYPE_BUY; } MqlTradeRequest request = {0}; MqlTradeResult result = {0}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.type = orderType; request.price = price; request.position = PositionGetInteger(POSITION_TICKET); request.deviation= Slippage; request.magic = MagicNumber; if(!OrderSend(request, result)) { Print("平仓失败,错误码:", result.retcode); return false; } Print("平仓成功,订单号:", result.order); return true; } //+------------------------------------------------------------------+ //| 去初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // 释放指标句柄 if(emaHandle != INVALID_HANDLE) IndicatorRelease(emaHandle); if(macdHandle != INVALID_HANDLE) IndicatorRelease(macdHandle); } ``` **要点提示:** - ♻️ 用完的指标句柄一定要释放,否则会造成资源泄漏。 - ♻️ `OnDeinit`在EA被移除、图表切换、周期切换时都会被调用。 --- ## 🧪 四、回测与优化:EA写好了只是开始 EA写好了,不代表就能赚钱了。接下来才是重头戏:回测和优化。 ### 回测注意事项 1. **数据质量很重要** - 尽量用高质量的历史数据 - 至少回测3-5年的数据 - 包含不同的市场环境(牛市、熊市、震荡市) 2. **参数不要过度优化** - 不要为了拟合历史数据而把参数调得太极端 - 保持参数的逻辑性和合理性 - 建议用样本内优化,样本外验证 3. **注意滑点和手续费** - 回测时一定要设置合理的滑点和手续费 - 实际交易中的成本会比回测高,保守一点没坏处 4. **关注风险指标,不要只看收益率** - 最大回撤:最好控制在20%以内 - 夏普比率:越高越好,至少要大于1 - 盈亏比:至少要1.5以上 - 胜率:趋势策略40%左右就不错了 ### 常见的回测陷阱 ⚠️ **过度拟合**:参数调得太"完美",只适合历史数据,实盘就亏。 ⚠️ **未来函数**:用了未来的数据来判断过去的信号,回测超准,实盘没用。 ⚠️ **偷价**:在K线收盘价入场,但实际交易中你不可能刚好在收盘价成交。 ⚠️ **忽略点差**:有些品种点差很大,尤其是交叉盘,频繁交易成本很高。 --- ## 💡 五、进阶优化方向 这个基础版EA只是一个起点。如果你想让它变得更好,可以从以下几个方向优化: ### 1. 资金管理优化 - 按账户资金百分比计算仓位(固定 fractional 模式) - 凯利公式计算最优仓位 - 根据波动率动态调整仓位(ATR仓位管理) ### 2. 入场过滤优化 - 增加波动率过滤(震荡市减少交易) - 增加多时间框架确认(大周期看涨+小周期入场) - 增加成交量过滤(突破要有量能配合) - 避开重大新闻事件(非农、利率决议等) ### 3. 出场策略优化 - 移动止盈(盈利到一定程度后收紧止盈) - 时间止损(持仓多久没盈利就平仓) - 反向信号平仓(出现反向信号就平仓) - 多策略组合出场 ### 4. 风险控制优化 - 每日最大亏损限制 - 最大持仓数量限制 - 连续亏损后降低仓位 - 账户最大回撤限制 --- ## ⚠️ 六、重要提醒 最后,作为一个做了多年EA开发的"老司机",给你几个忠告: **1. 没有完美的EA** 任何策略都有失效的时候,不要相信什么"永久盈利""年化100%"的鬼话。 **2. 回测好不代表实盘好** 回测只是起点,实盘表现通常会比回测差30%-50%,这是正常的。 **3. 不要过度优化** 参数调来调去,最后调出来的只是最适合历史数据的参数,不是最适合未来的。 **4. 做好资金管理** 这是唯一你能完全控制的事情。再好用的EA,仓位太重也会爆仓。 **5. 持续监控和维护** 市场在变,策略也会失效。定期检查EA的表现,必要时调整参数甚至暂停使用。 --- ## 📝 完整代码 由于篇幅原因,完整代码我整理成了独立文件。如果你需要,可以关注我的视频号,私信"趋势EA"获取。 当然,更建议你照着这篇文章自己写一遍——只有自己敲过的代码,才是真正理解了的。 --- > 💡 **最后想说的话**
>
> EA不是圣杯,但它是交易者的好帮手。它能帮你克服人性的弱点,严格执行交易纪律,24小时监控市场。
>
> 但记住:**EA只是工具,真正决定交易成败的,还是工具背后的人。**
>
> 希望这篇文章能帮你在EA开发的路上少走一点弯路。如果你觉得有帮助,欢迎分享给更多的交易者朋友。

---

🎬 **关注晓辉编程视频号**
MT4/MT5 EA开发实战 | 交易策略分享 | 编程技巧干货
微信搜索「晓辉编程」

💬 **添加晓辉为好友**
免费获取交易工具包 | 一对一EA定制咨询 | 加入交易者交流群
微信号:xiaohui_biancheng

分享海报
分享:

相关文章

  • EA交易实战避坑指南:从回测到实盘,8个多数交易者都会踩的EA常见陷阱

    EA交易实战避坑指南:从回测到实盘,8个多数交易者都会踩的EA常见陷阱

    晓辉编程 晓辉编程 MT4软件使用, MT5软件使用, 交易经验杂谈2026-06-25 15:09005280
  • 如何在 MQL4/MQL5 调用 C# dll

    如何在 MQL4/MQL5 调用 C# dll

    晓辉编程 晓辉编程 MT4编程方法, MT5编程方法1年前1107.61W0
晓辉编程

晓辉编程

专注MT4/MT5黄金外汇指标EA脚本程序设计与开发!

感谢您的关注,晓辉编程团队是一个有15年交易经验和10年程序化设计经验的团队,具有非常丰富的经验,专注于指标EA脚本的程序化设计开发。如果您正好有需求,我们将是您值得信赖的合作团队之一。

工作时间: 06:00-23:00

网站:www.eafxtech.com

手机:18511093950

q q:964063050

  • 文章249
  • 视频11
  • 下载44
  • 专题5
  • 快讯12

晓辉编程团队

晓辉编程团队创建于2010年11月,是一个专注于MT4/MT5指标EA脚本开发的团队!

晓辉晓辉编程团队

联系我们

  • 18511093950
  • 964063050@qq.com
  • 周一至周六 09:00-22:00
  • 北京市丰台区

MT4/MT5定制模版

  • MT4/MT5 多货币网格趋势交易系统EA
  • MT4/MT5 单货币马丁对冲交易系统EA
  • MT4/MT5 BBand趋势刷单策略系统EA
  • MT4/MT5 账户监控QQ信息提示EA

MT4/MT5免费指标

  • MT4-货币强弱指标(MADdash)
  • MT4-商品隔夜利息指标
  • MT4-历史交易订单统计指标
  • MT5-交易路径指标

MT4/MT5商业EA

  • MT4/MT5-订单同步交易系统EA
  • MT4-多货币持仓订单信息统计及开平仓系统EA
  • MT4-账户订单监控系统EA
  • MT4-移动挂单网格对冲财经日历系统EA

晓辉编程团队

微信扫码联系我们
Copyright © 2012-至今 晓辉编程 京ICP备17010782号-1 本网站仅提供软件开发技术服务,不涉及任何交易平台运营或经纪业务。 MT4、MT5 是 MetaQuotes Software Corp. 的注册商标,本网站仅提供相关技术开发服务。
27 次查询在 1.844 秒, 使用 58.53MB 内存