保持敬畏之心
交易是一场持久战

MQL4(58):采用挂单策略的进阶版EA程序

本节展示的是一款采用挂单(止损单)方式进行交易,并融合了更多高级特性的EA程序代码。

相比于上一版,从市价单策略改为挂单策略,增加了更完善的市场状态检查、更严格的订单验证、改进了价格计算逻辑、增加了保证金检查、错误处理更加完善、订单管理更加严谨。
这些改进使得EA程序在交易执行上更加安全和稳健。
需要注意的是,一个完善的EA是需要不断修改与完善的,先拥有框架,再添砖加瓦,然后再修复细微处的逻辑错误。一段代码的逻辑是否正常,不是一开始就能拍着胸脯保证的,是需要逐渐改良和升级的。文章内提供的源码并不能直接用于实盘,仅作为教学演示用。
#property copyright "tudaojiaoyi" // 版权声明
#include <stdlib.mqh> // 引入标准库

// ===== 外部输入参数 =====
extern int      PendingPips  = 20;    // 挂单距离 (点数)
extern double   LotSize      = 0.1;   // 交易手数
extern double   StopLoss     = 50;    // 止损点数
extern double   TakeProfit   = 100;   // 止盈点数
extern int      Slippage     = 5;     // 滑点
extern int      MagicNumber  = 123;   // 魔术数字,用于标识EA订单
extern int      FastMAPeriod = 10;    // 快速移动平均线周期
extern int      SlowMAPeriod = 20;    // 慢速移动平均线周期

// ===== 全局变量 =====
int      BuyTicket;      // 用于存储买入订单的订单号
int      SellTicket;     // 用于存储卖出订单的订单号
double   UsePoint;       // 用于存储当前货币对的点值 (例如 0.0001 或 0.01)
int      UseSlippage;    // 用于存储根据经纪商位数调整后的滑点
int      ErrorCode;      // 用于存储上一个操作的错误代码
string   ErrDesc;        // 用于存储错误代码对应的描述信息

// ===== 初始化函数 =====
// EA加载时执行一次,用于初始化全局变量和设置
int OnInit()
{
     // 基础初始化
     UsePoint = PipPoint(Symbol());        // 计算并设置当前交易品种的点值
     UseSlippage = GetSlippage(Symbol(), Slippage); // 计算并设置当前交易品种的滑点
     return(INIT_SUCCEEDED);               // 返回初始化成功信号
}

// ===== 主体逻辑函数 =====
// 每个报价变动 (tick) 时执行一次,包含EA的主要交易逻辑
void OnTick()
{
     // 基础检查,确保交易环境正常
     if(!IsTradeAllowed()) return; // 检查EA是否被允许交易
     if(IsStopped()) return;       // 检查EA是否已停止运行
     if(!IsConnected()) return;    // 检查是否已连接到交易服务器

     // 检查是否有足够的可用保证金
     if(AccountFreeMargin() < (1000 * LotSize)) { // 简单地检查保证金是否足够,这里的1000是一个估算值
         Print("可用保证金不足");
         return;
     }

     // --- 计算移动平均线 (基于当前K线) ---
     double FastMA = iMA(NULL, 0, FastMAPeriod, 0, 0, 0, 0); // 计算快速移动平均线的值
     double SlowMA = iMA(NULL, 0, SlowMAPeriod, 0, 0, 0, 0); // 计算慢速移动平均线的值

     // --- 买入挂单逻辑 ---
     // 当快速均线上穿慢速均线,并且当前没有持有的买单或买入挂单时,执行买入逻辑
     if(FastMA > SlowMA && BuyTicket == 0)
     {
         // - 处理反向订单/挂单 -
         // 如果存在反向的卖单或卖出挂单,先将其平仓或删除
         if(SellTicket > 0)  // 首先检查订单号是否有效
         {
             // 尝试选择订单
             if(!OrderSelect(SellTicket, SELECT_BY_TICKET))
             {
                 ErrorCode = GetLastError();
                 ErrDesc = ErrorDescription(ErrorCode);
                 Print("订单选择失败 - 错误 ", ErrorCode, ": ", ErrDesc);
                 SellTicket = 0;  // 重置无效的订单号
                 return;
             }

             // 验证魔术数字,确保是本EA下的订单
             if(OrderMagicNumber() != MagicNumber)
             {
                 Print("订单魔术数字不匹配 - 订单号: ", SellTicket);
                 SellTicket = 0; // 如果魔术数字不匹配,则重置订单号,不再管理此订单
                 return;
             }

             // 验证订单是否已关闭
             if(OrderCloseTime() != 0)
             {
                 Print("订单已关闭 - 订单号: ", SellTicket);
                 SellTicket = 0; // 如果订单已关闭,则重置订单号
                 return;
             }

             // 根据订单类型执行不同操作:平仓或删除挂单
             if(OrderType() == OP_SELL) // 如果是已成交的卖单
             {
                 double CloseLots = OrderLots(); // 获取订单手数
                 while(IsTradeContextBusy()) Sleep(10); // 如果交易上下文正忙,则等待
                 RefreshRates(); // 刷新报价
                 double ClosePrice = Ask; // 使用当前的买价(Ask)来平掉卖单
                 bool Closed = OrderClose(SellTicket, CloseLots, ClosePrice, UseSlippage, Red); // 执行平仓操作
                 if(!Closed) // 如果平仓失败
                 {
                     ErrorCode = GetLastError();
                     ErrDesc = ErrorDescription(ErrorCode);
                     Alert("关闭卖单失败(买入挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
                     Print("关闭卖单失败详情 - Ask: ", Ask, " 手数: ", CloseLots, " 订单号: ", SellTicket);
                 }
                 else SellTicket = 0; // 平仓成功后,重置订单号
             }
             else if(OrderType() == OP_SELLSTOP) // 如果是卖出挂单
             {
                 bool Deleted = OrderDelete(SellTicket, Red); // 删除挂单
                 if(Deleted) SellTicket = 0; // 删除成功后,重置订单号
                 else // 如果删除失败
                 {
                     ErrorCode = GetLastError();
                     ErrDesc = ErrorDescription(ErrorCode);
                     Alert("删除卖出挂单失败(买入挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
                     Print("删除卖出挂单失败详情 - Ticket: ", SellTicket);
                 }
             }
         }

         // - 设置买入止损挂单 (Buy Stop) -
         double BuyStopLevelPts  = MarketInfo(Symbol(), MODE_STOPLEVEL); // 获取服务器要求的最小止损距离(点数)
         double BuyMinStopDist   = BuyStopLevelPts * Point; // 将最小止损距离转换为价格单位
         if (BuyMinStopDist < 5 * UsePoint) BuyMinStopDist = 5 * UsePoint; // 确保最小距离至少为5个点

         RefreshRates(); // 刷新报价
         double CurrentAskForStop = Ask; // 获取当前买价

         // 计算挂单价格,设置在当前K线最高点上方指定点数的位置
         double BuyPendingPrice = High[0] + (PendingPips * UsePoint); 
         // 如果计算出的挂单价格离当前市场价太近,不满足最小距离要求
         if(BuyPendingPrice <= CurrentAskForStop + BuyMinStopDist)
         {
             // 则将挂单价格调整到满足最小距离要求的位置
             BuyPendingPrice = CurrentAskForStop + BuyMinStopDist + Point;
         }

         double BuyStopLoss   = 0; // 初始化止损价格
         double BuyTakeProfit = 0; // 初始化止盈价格

         if(StopLoss > 0) BuyStopLoss = BuyPendingPrice - (StopLoss * UsePoint); // 如果设置了止损,计算止损价格
         if(TakeProfit > 0) BuyTakeProfit = BuyPendingPrice + (TakeProfit * UsePoint); // 如果设置了止盈,计算止盈价格

         // 检查并调整止损价格,确保其满足服务器的最小止损距离要求
         if(BuyStopLoss > 0 && BuyStopLoss >= BuyPendingPrice - BuyMinStopDist)
         {
             BuyStopLoss = BuyPendingPrice - BuyMinStopDist - Point;
         }
         if (BuyStopLoss == 0 && StopLoss > 0) { // 如果计算出的止损为0但用户设置了止损点数
             if (BuyPendingPrice - BuyMinStopDist > 0) BuyStopLoss = BuyPendingPrice - BuyMinStopDist; // 设置一个最小距离的止损
         }

         // 检查并调整止盈价格,确保其满足服务器的最小止损距离要求
         if(BuyTakeProfit > 0 && BuyTakeProfit <= BuyPendingPrice + BuyMinStopDist)
         {
             BuyTakeProfit = BuyPendingPrice + BuyMinStopDist + Point;
         }
         if (BuyTakeProfit == 0 && TakeProfit > 0) { // 如果计算出的止盈为0但用户设置了止盈点数
             BuyTakeProfit = BuyPendingPrice + BuyMinStopDist; // 设置一个最小距离的止盈
         }

         while(IsTradeContextBusy()) Sleep(10); // 等待交易上下文空闲
         // 发送买入挂单指令
         BuyTicket = OrderSend(Symbol(), OP_BUYSTOP, LotSize, BuyPendingPrice, UseSlippage,
                               BuyStopLoss, BuyTakeProfit, "Buy Stop Order", MagicNumber, 0, Green);
         if(BuyTicket == -1) // 如果下单失败
         {
             ErrorCode = GetLastError();
             ErrDesc = ErrorDescription(ErrorCode);
             Alert("开立买入挂单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
             Print("开立买入挂单失败详情 - Ask: ", Ask, " 手数: ", LotSize, " 挂单价: ", DoubleToStr(BuyPendingPrice,Digits),
                   " SL: ", DoubleToStr(BuyStopLoss,Digits), " TP: ", DoubleToStr(BuyTakeProfit,Digits));
         }
         SellTicket = 0; // 重置
     }

     // --- 卖出挂单逻辑 (与买入逻辑对称) ---
     // 当快速均线下穿慢速均线,并且当前没有持有的卖单或卖出挂单时,执行卖出逻辑
     if(FastMA < SlowMA && SellTicket == 0)
     {
         // - 处理反向订单/挂单 -
         // 如果存在反向的买单或买入挂单,先将其平仓或删除
         if(BuyTicket > 0)   // 首先检查订单号是否有效
         {
             // 尝试选择订单
             if(!OrderSelect(BuyTicket, SELECT_BY_TICKET))
             {
                 ErrorCode = GetLastError();
                 ErrDesc = ErrorDescription(ErrorCode);
                 Print("订单选择失败 - 错误 ", ErrorCode, ": ", ErrDesc);
                 BuyTicket = 0;   // 重置无效的订单号
                 return;
             }

             // 验证魔术数字
             if(OrderMagicNumber() != MagicNumber)
             {
                 Print("订单魔术数字不匹配 - 订单号: ", BuyTicket);
                 BuyTicket = 0;
                 return;
             }

             // 验证订单是否已关闭
             if(OrderCloseTime() != 0)
             {
                 Print("订单已关闭 - 订单号: ", BuyTicket);
                 BuyTicket = 0;
                 return;
             }

             // 验证订单类型
             if(OrderType() == OP_BUY)   // 如果是已成交的买单
             {
                 double SellCloseLots = OrderLots();
                 while(IsTradeContextBusy()) Sleep(10);
                 RefreshRates();
                 double SellClosePrice = Bid; // 使用当前的卖价(Bid)来平掉买单
                 bool SellClosed = OrderClose(BuyTicket, SellCloseLots, SellClosePrice, UseSlippage, Red);
                 if(!SellClosed)
                 {
                     ErrorCode = GetLastError();
                     ErrDesc = ErrorDescription(ErrorCode);
                     Alert("关闭买单失败(卖出挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
                     Print("关闭买单失败详情 - Bid: ", Bid, " 手数: ", SellCloseLots, " 订单号: ", BuyTicket);
                 }
                  else BuyTicket = 0;
             }
             else if(OrderType() == OP_BUYSTOP) // 如果是买入止损挂单
             {
                 while(IsTradeContextBusy()) Sleep(10);
                 bool BuyDeleted = OrderDelete(BuyTicket, Red);
                 if(BuyDeleted) BuyTicket = 0;
                 else
                 {
                     ErrorCode = GetLastError();
                     ErrDesc = ErrorDescription(ErrorCode);
                     Alert("删除买入挂单失败(卖出挂单前) - 错误 ", ErrorCode, ": ", ErrDesc);
                     Print("删除买入挂单失败详情 - Ticket: ", BuyTicket);
                 }
             }
         }

         // - 设置卖出止损挂单 (Sell Stop) -
         double SellStopLevelPts  = MarketInfo(Symbol(), MODE_STOPLEVEL);
         double SellMinStopDist   = SellStopLevelPts * Point;
         if (SellMinStopDist < 5 * UsePoint) SellMinStopDist = 5 * UsePoint;

         RefreshRates();
         double CurrentBidForStop = Bid;

         double SellPendingPrice = Low[0] - (PendingPips * UsePoint); // 挂单价格在当前K线最低点下方
         // 如果计算出的挂单价格离当前市场价太近
         if(SellPendingPrice >= CurrentBidForStop - SellMinStopDist)
         {
             // 调整挂单价以满足最小距离要求
             SellPendingPrice = CurrentBidForStop - SellMinStopDist - Point;
         }

         double SellStopLoss   = 0; // 初始化止损价格
         double SellTakeProfit = 0; // 初始化止盈价格

         if(StopLoss > 0) SellStopLoss = SellPendingPrice + (StopLoss * UsePoint); // 计算止损价
         if(TakeProfit > 0) SellTakeProfit = SellPendingPrice - (TakeProfit * UsePoint); // 计算止盈价

         // 检查并调整止损价格
         if(SellStopLoss > 0 && SellStopLoss <= SellPendingPrice + SellMinStopDist)
         {
             SellStopLoss = SellPendingPrice + SellMinStopDist + Point;
         }
         if (SellStopLoss == 0 && StopLoss > 0) {
             if (SellPendingPrice + SellMinStopDist < 99999) SellStopLoss = SellPendingPrice + SellMinStopDist;
         }

         // 检查并调整止盈价格
         if(SellTakeProfit > 0 && SellTakeProfit >= SellPendingPrice - SellMinStopDist)
         {
             SellTakeProfit = SellPendingPrice - SellMinStopDist - Point;
         }
         if (SellTakeProfit == 0 && TakeProfit > 0) {
             if (SellPendingPrice - SellMinStopDist > 0) SellTakeProfit = SellPendingPrice - SellMinStopDist;
         }

         while(IsTradeContextBusy()) Sleep(10);
         // 发送卖出挂单指令
         SellTicket = OrderSend(Symbol(), OP_SELLSTOP, LotSize, SellPendingPrice, UseSlippage,
                                SellStopLoss, SellTakeProfit, "Sell Stop Order", MagicNumber, 0, Red);
         if(SellTicket == -1)
         {
             ErrorCode = GetLastError();
             ErrDesc = ErrorDescription(ErrorCode);
             Alert("开立卖出挂单失败 - 错误 ", ErrorCode, ": ", ErrDesc);
             Print("开立卖出挂单失败详情 - Bid: ", Bid, " 手数: ", LotSize, " 挂单价: ", DoubleToStr(SellPendingPrice,Digits),
                   " SL: ", DoubleToStr(SellStopLoss,Digits), " TP: ", DoubleToStr(SellTakeProfit,Digits));
         }
         BuyTicket = 0; // 重置
     }
}

// ===== 辅助函数:计算点值 =====
// (与之前EA中的 PipPoint 函数相同)
// 根据货币对的小数位数确定其点值(例如 EURUSD 是 0.0001, USDJPY 是 0.01)
double PipPoint(string Currency)
{
     int      CalcDigits = MarketInfo(Currency, MODE_DIGITS); // 获取小数位数
     double   CalcPoint  = 0.0;
     // 如果是2位或3位小数(如USDJPY)
     if(CalcDigits == 2 || CalcDigits == 3) CalcPoint = 0.01;
     // 如果是4位或5位小数(如EURUSD)
     else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001;
     return(CalcPoint); // 返回计算出的点值
}

// ===== 辅助函数:获取滑点 =====
// (与之前EA中的 GetSlippage 函数相同)
// 根据经纪商是4位数还是5位数报价,来调整滑点参数
int GetSlippage(string Currency, int SlippagePips)
{
     int CalDigits    = MarketInfo(Currency, MODE_DIGITS); // 获取小数位数
     int CalcSlippage = 0;
     // 对于4位数报价的经纪商,滑点值不变
     if(CalDigits == 2 || CalDigits == 4) CalcSlippage = SlippagePips;
     // 对于5位数报价的经纪商,滑点值需要乘以10
     else if(CalDigits == 3 || CalDigits == 5) CalcSlippage = SlippagePips * 10;
     return(CalcSlippage); // 返回调整后的滑点
}
赞(0)
未经允许不得转载:图道交易 » MQL4(58):采用挂单策略的进阶版EA程序
分享到