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

​MQL4(17):写一个简单的EA示例

写一个简单的EA示例 (MA金叉死叉 – 市价单入场版)

下面我们通过一个完整的EA示例,来整合运用前面讨论过的各种概念和函数。这是一个基于移动平均线交叉的简单交易系统:

  • 入场信号:
    • 当10日MA均线上穿20日均线时 (金叉),买入信号。
    • 当10日MA均线下穿20日均线时 (死叉),卖出信号。
  • 交易逻辑:
    • 根据信号开立市价单 (Buy 或 Sell)。
    • 仓位管理: 设置为单向持仓,即同一时间最多只持有一个买单或一个卖单。
    • 当出现新的买入信号时,如果当前持有卖单,则先将卖单平仓,然后再开立新的买单。
    • 当出现新的卖出信号时,如果当前持有买单,则先将买单平仓,然后再开立新的卖单。
    • 使用全局变量 g_BuyTicketg_SellTicket 来跟踪记录当前持有的买单或卖单的订单号 (0 表示无持仓)。这个机制可以有效防止在同一方向上连续重复开仓。
    • 订单的退出由止损、止盈或反向开仓信号触发平仓来完成。
#property copyright "图道交易"
#property link      "https://www.eamql5.com"

// --- 输入参数 ---
extern double LotSize      = 0.1;   // 交易手数
extern double StopLoss     = 50;    // 止损点数 (pips)
extern double TakeProfit   = 100;   // 止盈点数 (pips)
extern int    Slippage     = 5;     // 允许滑点 (pips) - 会被转换为 points
extern int    MagicNumber  = 123;   // 魔术号 (用于区分EA订单)
extern int    FastMAPeriod = 10;   // 快速MA均线
extern int    SlowMAPeriod = 20;   // 慢速MA均线

// --- 全局变量 ---
int    g_BuyTicket      = 0;     // 存储当前买单订单号 (0表示无)
int    g_SellTicket     = 0;     // 存储当前卖单订单号 (0表示无)
double g_onePipValue    = 0.0;   // 存储1 pip对应的价格值
int    g_slippagePoints = 0;   // 存储转换后的滑点points值

// --- 初始化函数 ---
int init()
{
   // 计算并存储用于后续计算的 1 pip 值和滑点 points 值
   g_onePipValue  = PipPoint(Symbol());
   g_slippagePoints = GetSlippage(Symbol(), Slippage);

   // 打印信息以便调试确认
   PrintFormat("EA '%s' 初始化完成: 交易品种 %s, 1 Pip = %f, Slippage Points = %d",
               WindowExpertName(), Symbol(), g_onePipValue, g_slippagePoints);

   return(INIT_SUCCEEDED); // 返回初始化成功状态
}

// --- 核心逻辑函数 ---
void OnTick() // 
{
   // --- 1. 计算指标 ---
   // 获取当前 K 线 (索引 0) 的快速和慢速移动平均值
   double FastMA = iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
   double SlowMA = iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

   // --- 2. 买入逻辑 ---
   // 条件: 金叉 (Fast > Slow) 且当前没有持有买单 (g_BuyTicket == 0)
   if (FastMA > SlowMA && g_BuyTicket == 0)
   {
      // a. 先尝试平掉可能存在的反向卖单
      bool sellClosedOrNotExist = CloseOppositeOrder(g_SellTicket, OP_SELL);

      // b. 确认已无反向卖单后,准备开立买单
      if (sellClosedOrNotExist)
      {
          // 计算开仓价、止损价、止盈价
          double openPrice_Buy = Ask; // 买单开仓价
          double stopLoss_Buy = 0;
          double takeProfit_Buy = 0;
          if (StopLoss > 0) 
            stopLoss_Buy = NormalizeDouble(openPrice_Buy - (StopLoss * g_onePipValue), Digits);
          if (TakeProfit > 0) 
            takeProfit_Buy = NormalizeDouble(openPrice_Buy + (TakeProfit * g_onePipValue), Digits);

          // c. 发送开仓指令
          int ticket = OrderSend(Symbol(), OP_BUY, LotSize, openPrice_Buy, g_slippagePoints,
                                 stopLoss_Buy, takeProfit_Buy, "MA Cross Buy", MagicNumber, 0, Green);

          // d. 处理开仓结果
          if (ticket > 0) { // 开仓成功
              g_BuyTicket = ticket; // 记录新买单的订单号
              Print("成功开立买单 #", g_BuyTicket);
          } else { // 开仓失败
              Print("开立买单失败, Error: ", GetLastError());
          }
      } // end if sellClosedOrNotExist
   } // end if (金叉 && 无买单)

   // --- 3. 卖出逻辑 ---
   // 条件: 死叉 (Fast < Slow) 且当前没有持有卖单 (g_SellTicket == 0)
   if (FastMA < SlowMA && g_SellTicket == 0)
   {
      // a. 先尝试平掉可能存在的反向买单
      bool buyClosedOrNotExist = CloseOppositeOrder(g_BuyTicket, OP_BUY);

      // b. 确认已无反向买单后,准备开立卖单
      if (buyClosedOrNotExist)
      {
          // 计算开仓价、止损价、止盈价
          double openPrice_Sell = Bid; // 卖单开仓价
          double stopLoss_Sell = 0;
          double takeProfit_Sell = 0;
          if (StopLoss > 0) 
            stopLoss_Sell = NormalizeDouble(openPrice_Sell + (StopLoss * g_onePipValue), Digits);
          if (TakeProfit > 0) 
            takeProfit_Sell = NormalizeDouble(openPrice_Sell - (TakeProfit * g_onePipValue), Digits);

          // c. 发送开仓指令
          ticket = OrderSend(Symbol(), OP_SELL, LotSize, openPrice_Sell, g_slippagePoints,
                                 stopLoss_Sell, takeProfit_Sell, "MA Cross Sell", MagicNumber, 0, Red);

          // d. 处理开仓结果
          if (ticket > 0) { // 开仓成功
              g_SellTicket = ticket; // 记录新卖单的订单号
              Print("成功开立卖单 #", g_SellTicket);
          } else { // 开仓失败
              Print("开立卖单失败, Error: ", GetLastError());
          }
      } // end if buyClosedOrNotExist
   } // end if (死叉 && 无卖单)
}

// ---  封装平仓/删单函数 ---
// 功能:尝试平掉指定市价单,并更新订单变量
// 返回值: true 表示反向订单已关闭或不存在,可以继续开新单; false 表示平仓失败
bool CloseOppositeOrder(int& ticketVar, int orderTypeToClose) {
    bool closedOrCleared = true; // 默认为可以继续
    if (ticketVar > 0) {
        if (OrderSelect(ticketVar, SELECT_BY_TICKET)) {
            if (OrderCloseTime() == 0) { // 订单未关闭
                if (OrderType() == orderTypeToClose) { // 确认是目标类型的市价单
                    double closePrice = (orderTypeToClose == OP_BUY) ? Bid : Ask;
                    RefreshRates(); // 确保价格最新
                    if (OrderClose(ticketVar, OrderLots(), closePrice, g_slippagePoints, Red)) {
                        Print("成功发送平仓指令 for #", ticketVar);
                        ticketVar = 0; // 指令发送成功,清除票号记录
                        closedOrCleared = true;
                    } else {
                        Print("发送平仓指令失败 for #", ticketVar, " Error: ", GetLastError());
                        closedOrCleared = false; // 平仓失败,本次不能开新单
                    }
                } else { // 类型不符
                    Print("订单 #", ticketVar, " 类型不是期望的 ", orderTypeToClose, ", 清除记录.");
                    ticketVar = 0;
                    closedOrCleared = true;
                }
            } else { // 订单已被关闭
                ticketVar = 0; // 清除票号记录
                closedOrCleared = true;
            }
        } else { // 无法选中订单
            ticketVar = 0; // 清除票号记录
            closedOrCleared = true;
        }
    }
    return closedOrCleared;
}

// --- 自定义辅助函数 ---
double PipPoint(string currencySymbol)
{
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);
    double p = Point;
    if (digits == 3 || digits == 5) { p *= 10.0; }
    return(p);
}

int GetSlippage(string currencySymbol, int slippageInPips)
{
    int digits = (int)MarketInfo(currencySymbol, MODE_DIGITS);
    int points = slippageInPips;
    if (digits == 3 || digits == 5) { points *= 10; }
    return(points);
}

(注:以上意译版本的 EA 代码中包含了建议的 CloseOppositeOrder 辅助函数,并修正了 PipPointGetSlippage 函数的实现方式以提高准确性和兼容性。实际使用时,需要将这些辅助函数包含在 EA 代码中。)

将 EA 修改为使用挂单

现在,我们将之前的 MA 交叉 EA 进行修改,使其不再使用市价单,而是使用挂单,具体来说,是止损挂单。

  • 新逻辑概述:
    • 当快速 MA > 慢速 MA 时,不再立即买入,而是在当前价格上方一定距离处设置一个买入止损单 (Buy Stop)
    • 当快速 MA < 慢速 MA 时,在当前价格下方一定距离处设置一个卖出止损单 (Sell Stop)
  • 挂单距离: 引入一个新的外部参数 PendingPips 来定义挂单价格距离某个基准价的点数 (pips)。
// 添加新的外部参数
extern int PendingPips = 10; // 挂单距离当前价格的点数 (pips)
  • 订单管理修改: 当产生新的挂单信号时,不仅要处理可能存在的反向市价单,还要处理可能存在的反向挂单。因此,在尝试设置新挂单前,需要检查反向订单的类型 (OrderType()),然后调用正确的函数来结束它:市价单用 OrderClose(),挂单用 OrderDelete()

示例:修改后的平仓/删单逻辑 (在设置买挂单之前,处理反向卖单/卖挂单)

// 假设 g_SellTicket 存储了反向订单号
if (g_SellTicket > 0) // 如果存在反向订单记录
{
    if (OrderSelect(g_SellTicket, SELECT_BY_TICKET)) // 尝试选中
    {
        if (OrderCloseTime() == 0) // 确认订单还活跃
        {
            int orderType = OrderType();
            bool result = false;
            string action = "";
            if (orderType == OP_SELL) // 如果是市价卖单
            {
                result = OrderClose(g_SellTicket, OrderLots(), Ask, g_slippagePoints, Red); // 平仓
                action = "平掉市价卖单";
            }
            // 检查是否为卖挂单 (OP_SELLSTOP 或 OP_SELLLIMIT)
            else if (orderType == OP_SELLSTOP || orderType == OP_SELLLIMIT)
            {
                result = OrderDelete(g_SellTicket, Red); // 删除挂单
                action = "删除挂卖单";
            }

            if (result) { // 处理成功
                Print(action, " #", g_SellTicket, " 成功.");
                g_SellTicket = 0; // 清除记录
            } else { // 处理失败
                Print(action, " #", g_SellTicket, " 失败, Error: ", GetLastError());
                // 失败时暂不清除记录,下次 tick 可能重试
            }
        }
        else g_SellTicket = 0; // 订单已结束 (CloseTime != 0),清除记录
    }
    else g_SellTicket = 0; // 无法选中,清除记录
} // end if (g_SellTicket > 0)

// ... 只有当 g_SellTicket 最终为 0 时,才继续设置买挂单 ...

(处理反向买单/买挂单的逻辑类似,检查 g_BuyTicket,平买单用 Bid,删除买挂单)

  • 挂单价格计算与下单:

    • 计算挂单价格。示例中使用 Close[0] (上一根 K 线的收盘价) 作为计算基准价,这在逻辑上可能需要商榷。例如,设置买入止损单通常会基于当前 Ask 价或 High[0] 向上浮动。

     

    // 计算买入止损挂单价格 (高于 Close[0])
    // 注意: 使用 Close[0] 作为基准可能不符合常规逻辑,通常会用 High[0] 或 Ask
    double pendingPrice_Buy = NormalizeDouble(Close[0] + (PendingPips * g_onePipValue), Digits);
    
    • 计算相对于挂单价格的止损和止盈。
    • 调用 OrderSend() 时,使用挂单类型 (OP_BUYSTOP) 和计算出的 pendingPrice_Buy

    示例 (设置买入止损挂单):

    // ... (先执行上述平仓/删单逻辑,确保 g_SellTicket == 0) ...
    if (g_SellTicket == 0) // 确认没有反向订单/挂单后
    {
        // 计算挂单价格
        double pendingPrice_Buy = NormalizeDouble(Close[0] + (PendingPips * g_onePipValue), Digits);
    
        int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
        double minDistance = stopLevel * Point;
        if ((pendingPrice_Buy - Ask) < minDistance)
          {
            Print("挂单价格距离市价太近 (StopLevel), 不挂单。");
          }
        // 计算相对于挂单价的 SL/TP
        double stopLoss_BuyStop = 0;
        double takeProfit_BuyStop = 0;
        if (StopLoss > 0) stopLoss_BuyStop = NormalizeDouble(pendingPrice_Buy - (StopLoss * g_onePipValue), Digits);
        if (TakeProfit > 0) takeProfit_BuyStop = NormalizeDouble(pendingPrice_Buy + (TakeProfit * g_onePipValue), Digits);
    
        // !!! 实际代码中还需检查挂单价格是否满足最小止损距离 !!!
        // 例如: if (pendingPrice_Buy <= Ask + MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) return(0);
    
        // 设置买入止损挂单
        int ticket = OrderSend(Symbol(), OP_BUYSTOP, LotSize, pendingPrice_Buy, g_slippagePoints,
                               stopLoss_BuyStop, takeProfit_BuyStop, "MA Cross Buy Stop", MagicNumber, 0, Green);
    
        if (ticket > 0) { // 挂单设置成功
            g_BuyTicket = ticket; // 记录新挂单的订单号
            Print("成功设置买入止损挂单 #", g_BuyTicket, " at ", pendingPrice_Buy);
        } else { // 挂单设置失败
            Print("设置买入止损挂单失败, Error: ", GetLastError());
        }
    } // end if g_SellTicket == 0
    

    卖出止损单的逻辑类似:

    • 先处理反向买单/买挂单。
    • 计算卖出止损挂单价格 (原书示例: Close[0] - (PendingPips * g_onePipValue))。
    • 计算相对于挂单价的 SL/TP。
    • 检查最小距离。
    • 调用 OrderSend() 设置 OP_SELLSTOP 挂单,成功后记录到 g_SellTicket 并将 g_BuyTicket 置 0。
赞(0)
未经允许不得转载:图道交易 » ​MQL4(17):写一个简单的EA示例