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

​MQL4(18):OrderModify() - 订单修改函数

在MQL4中为市价单设置止损(Stop Loss) 和止盈 (Take Profit) ,传统方法是直接在调用 OrderSend() 函数时就将 SL/TP 价格作为参数传入。虽然这种方式对于大多数经纪商运行良好,但是一些账户不支持在提交市价单 (OP_BUY/OP_SELL) 的同时设置止损和止盈。如果在这些平台上强行这样做,OrderSend() 可能会失败并返回错误。

因此,为了确保EA具有更好的兼容性,特别是能够适应各种环境,推荐采用两步法来为市价单设置 SL/TP:

  1. 先调用 OrderSend() 发送一个不带SL/TP(即将 SL/TP 参数设为 0)的市价单。
  2. 在确认订单成功开立后,再调用 OrderModify() 函数来添加或修改该订单的SL 和TP。

注意:这个问题通常仅限于市价单。对于挂单通常可以在调用 OrderSend() 时就直接设置好止损和止盈价格。

OrderModify() – 订单修改函数

在订单成功提交(无论是市价单还是挂单)之后,您可以使用 OrderModify() 函数来更改该订单的某些属性,主要包括:

  • 止损价格
  • 止盈价格
  • 挂单的触发价格
  • 挂单的到期时间

要使用 OrderModify(),必须先知道要修改订单的订单号

其函数语法如下:

bool OrderModify(
   int      ticket,           // 要修改的订单的订单号
   double   price,            // 【仅对挂单有效】新的挂单触发价格
   double   stoploss,         // 新的止损价格
   double   takeprofit,       // 新的止盈价格
   datetime expiration,       // 【仅对挂单有效】新的到期时间
   color    arrow_color=clrNONE // 可选,标记修改位置的箭头颜色
);

返回值 (bool 类型):

  • true: 修改指令已成功发送。
  • false: 修改指令发送失败。调用 GetLastError() 获取原因。

使用OrderModify()的注意事项:

  1. 订单状态: 必须确保您要修改的订单仍然是活动状态(未平仓的市价单或未触发/未过期的挂单)。不能修改已平仓或已取消的订单。
  2. 挂单价格修改: 只能修改尚未被触发的挂单的触发价格 (price 参数)。修改后的价格同样需要满足与当前市价的最小距离要求 (MODE_STOPLEVEL)。
  3. SL/TP 有效性: 新设置的止损和止盈价格必须是有效的。例如,买单的止损价必须低于当前 Ask 价(或挂单价),止盈价必须高于当前 Ask 价(或挂单价),并且都需要满足 MODE_STOPLEVEL 的最小距离要求。
  4. 保持未修改参数的原始值: 这是极其重要的一点。当您只想修改订单的某一个或几个属性(例如,只想添加止损),对于不打算修改的参数,您必须传入它们在订单中当前的值。这意味着在调用 OrderModify() 之前,您通常需要先用 OrderSelect() 选中订单,然后调用相应的 Order*() 函数(如 OrderOpenPrice(), OrderTakeProfit(), OrderExpiration() 等)来获取这些未改变参数的当前值,并将它们作为参数传给 OrderModify()
  5. 错误 1 (无结果): 如果您调用 OrderModify() 时,传入的所有参数值(Price, StopLoss, TakeProfit, Expiration)都与订单当前的实际值完全相同(即没有进行任何有效修改),函数会调用失败并返回错误代码 1 (“no result” 或 “无效参数”)。虽然这个错误本身通常无害,但它表明您的代码逻辑可能存在问题。可以有条件地调用 OrderModify,仅在计算出的新值与当前值不同时才执行修改。

为现有市价单添加止损和止盈 

  1. 下单: 调用 OrderSend() 发送市价单,并将 stoplosstakeprofit 参数设置为 0。同时,获取返回的订单号并存入变量(例如 buyTicket)。
  2. 检查下单结果: 确认 buyTicket > 0,表示订单已成功发送。
  3. 选中订单: 使用 OrderSelect(buyTicket, SELECT_BY_TICKET) 选中刚刚创建的订单。
  4. 获取开仓价: 调用 OrderOpenPrice() 获取该订单的实际开仓价格。
  5. 计算 SL/TP: 根据获取到的开仓价和用户设置的 SL/TP 点数 (pips) 以及 g_onePipValue(之前计算好的代表1 pip的值),计算出实际的止损价和止盈价。
  6. 检查是否需要修改: 确认计算出的 newSLnewTP 大于 0 (即用户确实设置了 SL/TP) 并且与订单当前的 SL/TP 值不同。
  7. 调用 OrderModify: 调用 OrderModify() 函数,传入 buyTicketOrderOpenPrice() (保持开仓价不变),计算出的 newSLnewTP,以及 0 (保持到期时间不变)。

代码示例 (为买单添加 SL/TP):

// 假设 LotSize, Ask, g_slippagePoints, MagicNumber, StopLoss, TakeProfit, g_onePipValue 已定义且有效

// 1. 发送不带 SL/TP 的买单
int buyTicket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, g_slippagePoints, 0, 0,
                          "Buy Order (ECN)", MagicNumber, 0, Green);

// 2. 检查下单是否成功发送
if (buyTicket > 0)
{
    // 3. 尝试选中刚下的订单
    if (OrderSelect(buyTicket, SELECT_BY_TICKET))
    {
        // 4. 获取实际开仓价和当前 SL/TP (应为 0)
        double openPrice = OrderOpenPrice();
        double currentSL = OrderStopLoss();
        double currentTP = OrderTakeProfit();

        // 5. 计算新的 SL/TP 价格
        double newSL = 0;
        double newTP = 0;
        if (StopLoss > 0) newSL = NormalizeDouble(openPrice - (StopLoss * g_onePipValue), Digits);
        if (TakeProfit > 0) newTP = NormalizeDouble(openPrice + (TakeProfit * g_onePipValue), Digits);

        // 6. 检查是否有必要修改 (新值有效且与当前值不同)
        bool needModify = false;
        if (newSL > 0 && MathAbs(newSL - currentSL) > Point * 0.1) needModify = true; // 允许微小误差比较
        if (newTP > 0 && MathAbs(newTP - currentTP) > Point * 0.1) needModify = true;

        // 7. 如果需要修改,则调用 OrderModify
        if (needModify)
        {
            // 准备参数:保持开仓价和到期时间不变
            RefreshRates(); // (可选但推荐) 确保价格信息最新
            bool modifyResult = OrderModify(buyTicket, OrderOpenPrice(), newSL, newTP, 0, clrNONE); // 不显示修改箭头

            if (modifyResult)
            {
                Print("成功发送修改指令 (添加 SL/TP) for #", buyTicket);
            }
            else
            {
                Print("发送修改指令失败 for #", buyTicket, " Error:", GetLastError());
                // 可能需要错误处理逻辑,例如稍后重试
            }
        } // end if (needModify)
    }
    else
    {
        Print("OrderSelect 失败 for ticket #", buyTicket, " Error:", GetLastError());
        // 无法选中刚开的单?可能状态已改变,需要处理
    }
}
else
{
    Print("OrderSend 开买单失败, Error:", GetLastError());
}

结论与建议: 尽管为市价单设置 SL/TP 采用这种“先下单,后修改”的两步法会增加几行代码,但强烈推荐您在开发 EA 时使用此方法。主要原因确保您的 EA 在包括 ECN/STP 在内的各类经纪商平台上都能稳定运行。这种方法基于订单实际成交后的开仓价 (OrderOpenPrice()) 来计算 SL/TP,避免了 OrderSend() 时可能因滑点导致 SL/TP 相对位置不准确的问题。

修改挂单价格

OrderModify() 函数不仅可以用来修改止损和止盈,还可以用来调整尚未被触发的挂单 (Pending Order) 的入场价格 (price 参数)。但请注意,一旦挂单被市场价格触及并成功执行(即“成交”或“filled”),它就转变成了市价单,其开仓价格就不能再被修改了。

假设我们已经计算出了一个新的、期望的挂单价格,并存储在变量 newPendingPrice 中,同时确保了这个新价格是有效的(例如,满足最小距离要求)。以下是如何使用 OrderModify() 来修改一个现有挂单的入场价格,同时保持其止损、止盈和到期时间不变:

// int ticketToModify = ...;       // 要修改的挂单订单号
// double newPendingPrice = ...; // 计算好的新挂单价格

// 0. 定义变量
int    ticket         = ticketToModify;
double newPrice       = NormalizeDouble(newPendingPrice, _Digits); // 规范化新价格
string sym            = OrderSymbol(); // 获取订单的交易品种
int    orderType      = -1;
double currentAsk     = SymbolInfoDouble(sym, SYMBOL_ASK);
double currentBid     = SymbolInfoDouble(sym, SYMBOL_BID);
int    slippage       = 3; // 允许的滑点,对于挂单修改价格不是必须,但保持良好习惯
int    stopLevel      = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
double point          = SymbolInfoDouble(sym, SYMBOL_POINT);

// 1. 选中目标挂单
if (OrderSelect(ticket, SELECT_BY_TICKET))
{
    orderType = OrderType();
    // 2. 检查是否是未成交的挂单
    if (OrderCloseTime() == 0 && (orderType >= OP_BUYSTOP && orderType <= OP_SELLLIMIT))
    {
        // 3. 检查新价格是否与当前价格确实不同
        if (MathAbs(newPrice - NormalizeDouble(OrderOpenPrice(), _Digits)) > point * 0.1) // 允许微小误差比较
        {
            // 3.1 【重要】检查新价格是否满足 StopLevel 要求
            bool priceOk = false;
            RefreshRates(); // 尝试获取最新的服务器价格,以便进行更准确的StopLevel判断
            currentAsk = SymbolInfoDouble(sym, SYMBOL_ASK); // 再次获取,因为RefreshRates是异步的
            currentBid = SymbolInfoDouble(sym, SYMBOL_BID);

            switch(orderType)
            {
                case OP_BUYLIMIT:
                    if (newPrice < currentBid - stopLevel * point) priceOk = true;
                    break;
                case OP_BUYSTOP:
                    if (newPrice > currentAsk + stopLevel * point) priceOk = true;
                    break;
                case OP_SELLLIMIT:
                    if (newPrice > currentAsk + stopLevel * point) priceOk = true;
                    break;
                case OP_SELLSTOP:
                    if (newPrice < currentBid - stopLevel * point) priceOk = true;
                    break;
            }

            if (!priceOk)
            {
                Print("订单 #", ticket, " 新挂单价格 ", DoubleToString(newPrice, _Digits), 
                      " 不满足StopLevel要求 (StopLevel: ", stopLevel, " points). ",
                      "Current Ask: ", DoubleToString(currentAsk, _Digits), 
                      ", Current Bid: ", DoubleToString(currentBid, _Digits));
            }
            else
            {
                // 4. 调用 OrderModify 修改价格
                //    注意:需要传入当前的 SL, TP, Expiration 值以保持不变
                // RefreshRates(); // 已经在上面为StopLevel检查调用过了,这里可以省略,服务器会用它自己的价格验证
                bool result = OrderModify(
                                  ticket,              // 订单号
                                  newPrice,            // 新的挂单价格 (已规范化)
                                  OrderStopLoss(),     // 保持当前止损不变
                                  OrderTakeProfit(),   // 保持当前止盈不变
                                  OrderExpiration(),   // 保持当前到期时间不变
                                  clrNONE              // 不绘制修改箭头
                              );

                // 5. 处理修改结果
                if (result) {
                    Print("成功发送修改挂单价格指令 for #", ticket, " to ", DoubleToString(newPrice, _Digits));
                } else {
                    Print("修改挂单价格失败 for #", ticket, ", Error: ", GetLastError());
                    // 可以进一步分析常见的错误,如130 (ERR_INVALID_STOPS), 1 (ERR_NO_RESULT), 129 (ERR_PRICE_CHANGED)
                }
            }
        } else {
           Print("新价格 ", DoubleToString(newPrice, _Digits), " 与当前价格 ", DoubleToString(OrderOpenPrice(),_Digits), " 几乎相同,无需修改 for #", ticket);
        }
    } else {
        Print("订单 #", ticket, " 不是一个可修改的挂单 (Type: ", orderType, ", CloseTime: ", TimeToString(OrderCloseTime()), ").");
    }
} else {
    Print("无法选中订单 #", ticket, ", 错误代码: ", GetLastError());
}

 

赞(0)
未经允许不得转载:图道交易 » ​MQL4(18):OrderModify() - 订单修改函数
分享到