轻松快捷开发 MetaTrader 程序的函数库(第十部分):与 MQL4 的兼容性 - 开仓和激活挂单的事件·进阶篇
🔧

轻松快捷开发 MetaTrader 程序的函数库(第十部分):与 MQL4 的兼容性 - 开仓和激活挂单的事件·进阶篇

(2/3)· 从 34 个编译错误出发,把 MQL4 缺失的交易类与事件定义逐一补齐

实战向 第 2/3 篇
把 MQL5 的测试 EA 直接塞进 MT4 目录编译,大概率会撞上几十条报错,多数源于标准库没有交易类。先跑起来看哪段不工作,再反推函数库要补什么,比闷头改代码更省时间。

按订单号平仓的两个底层函数

在 MT5 的 MQL5 环境里,老式持仓平仓仍常借助 OrderClose 系列函数。下面这段 PositionClose 按 ticket 平仓,支持部分平仓:volume 传 0 或大于持仓量时,自动取 OrderLots() 全平;否则按指定手数平。 平仓价随持仓方向取 SYMBOL_BID(多单)或 SYMBOL_ASK(空单),deviation 默认 2 点。若 OrderCloseTime() 已大于 0,说明仓位已不在,函数直接返回 false,避免重复平仓报错。 PositionCloseBy 则是用反向单抵销平仓,入参除了自身 ticket 还要给 ticket_by。两者开头都先 OrderSelect 校验,失败就打印双语错误并返 false——这套写法在 EURUSD 这类点差 2~20 点的品种上,能减少因选单失败导致的异常。 外汇与贵金属杠杆高,平仓滑点可能吞掉短线利润,上 MT5 前先把 deviation 按品种波动调一调。

MQL5 / C++
class="type">bool PositionClose(const class="type">ulong ticket,const class="type">class="kw">double volume=class="num">0,const class="type">int deviation=class="num">2)
  {
   ResetLastError();
   if(!OrderSelect((class="type">int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
     }
   if(OrderCloseTime()>class="num">0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      class="kw">return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      class="kw">return false;
     }
   class="type">class="kw">double price=class="num">0;
   class="type">class="kw">color  clr=clrNONE;
   if(type==ORDER_TYPE_BUY)
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID);
      clr=clrBlue;
     }
   else
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK);
      clr=clrRed;
     }
   class="type">class="kw">double vol=(volume==class="num">0 || volume>OrderLots() ? OrderLots() : volume);
   ResetLastError();
   if(!OrderClose((class="type">int)ticket,vol,price,deviation,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
     }
   class="kw">return true;
  }

class="type">bool PositionCloseBy(const class="type">ulong ticket,const class="type">ulong ticket_by)
  {
   ResetLastError();
   if(!OrderSelect((class="type">int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
     }

「用持仓互抵与挂单删除收紧风控」

在 MT5 的 EA 逻辑里,想用一笔持仓去平掉另一笔反向持仓,核心函数是 OrderCloseBy。下面这段实现先校验自身 ticket 是否已成历史(OrderCloseTime()>0),再确认类型落在 ORDER_TYPE_BUY / ORDER_TYPE_SELL 区间内,越界就直接 return false,避免把挂单误当持仓处理。 紧接着用 OrderSelect 按 ticket_by 选中“对手仓”,同样跑一遍已平仓与类型检查。这里有个细节:clr 参数按原仓方向给 clrBlue 或 clrRed,只是图表标记色,不影响平仓结果,但漏写会在日志里让后续排查的人看花眼。

PendingOrderDelete 则是另一条路径:它只接挂单 ticket,类型判断反过来用 type<ORDER_TYPE_SELLtype>ORDER_TYPE_SELL_STOP 来拦掉市价持仓。外汇与贵金属杠杆高,这类“删单失败静默返回”的代码若不加警报,实盘可能倾向在你不知情时保留危险敞口。

开 MT5 把这段塞进自己的脚本,故意传一个已平仓 ticket,应该立刻在专家日志看到 'Position already closed' 或俄文版,证明守卫逻辑生效。

MQL5 / C++
  if(OrderCloseTime()>class="num">0)
    {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      class="kw">return false;
    }
  ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
  if(type>ORDER_TYPE_SELL)
    {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      class="kw">return false;
    }
  ResetLastError();
  if(!OrderSelect((class="type">int)ticket_by,SELECT_BY_TICKET))
    {
      Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
    }
  if(OrderCloseTime()>class="num">0)
    {
      Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed"));
      class="kw">return false;
    }
  ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType();
  if(type_by>ORDER_TYPE_SELL)
    {
      Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by);
      class="kw">return false;
    }
  class="type">class="kw">color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
  ResetLastError();
  if(!OrderCloseBy((class="type">int)ticket,(class="type">int)ticket_by,clr))
    {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
    }
  class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Remove a pending order by ticket                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool PendingOrderDelete(const class="type">ulong ticket)
  {
  ResetLastError();
  if(!OrderSelect((class="type">int)ticket,SELECT_BY_TICKET))
    {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
    }
  if(OrderCloseTime()>class="num">0)
    {
      Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted"));
      class="kw">return false;
    }
  ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
  if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
    {

◍ 挂单删除与持仓改仓的容错写法

这段逻辑把挂单删除和持仓修改拆成了两个独立函数,核心都在「先校验对象类型,再执行交易指令」。删除挂单前用 ORDER_TYPE_SELL_LIMIT 做分界:type 小于该枚举值(即买类挂单)标蓝,否则标红,方便在终端里一眼区分方向。 OrderDelete 之前必须 ResetLastError(),否则上一次错误码可能污染本次判断;删除失败直接返回 false 并打印 GetLastError() 的具体数值,外汇与贵金属杠杆交易高风险,这类显式报错能避免静默失效。 PositionModify 只接受 type<=ORDER_TYPE_SELL 的持仓,若选到挂单或已平仓单(OrderCloseTime()>0)立即退出。修改时把开仓价原样传回 OrderModify,仅动 sl/tp,挂单修改函数 PendingOrderModify 则额外接收 price_set 重设挂单价。 让小布替你跑这套:把下面代码贴进 MT5 脚本,用真实 ticket 测一次删除与改仓,看 Print 日志是否如预期输出错误码。

MQL5 / C++
  Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((class="type">ENUM_POSITION_TYPE)type)," #",ticket);
  class="kw">return false;
   }
  class="type">class="kw">color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
  ResetLastError();
  if(!OrderDelete((class="type">int)ticket,clr))
   {
     Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not class="kw">delete order. Error "),(class="type">class="kw">string)GetLastError());
     class="kw">return false;
   }
  class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Modify position by ticket                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool PositionModify(const class="type">ulong ticket,const class="type">class="kw">double sl,const class="type">class="kw">double tp)
  {
  ResetLastError();
  if(!OrderSelect((class="type">int)ticket,SELECT_BY_TICKET))
   {
     Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(class="type">class="kw">string)GetLastError());
     class="kw">return false;
   }
  ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
  if(type>ORDER_TYPE_SELL)
   {
     Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
     class="kw">return false;
   }
  if(OrderCloseTime()>class="num">0)
   {
     Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((class="type">ENUM_POSITION_TYPE)type)," #",ticket);
     class="kw">return false;
   }
  class="type">class="kw">color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
  ResetLastError();
  if(!OrderModify((class="type">int)ticket,OrderOpenPrice(),sl,tp,class="num">0,clr))
   {
     Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(class="type">class="kw">string)GetLastError());
     class="kw">return false;
   }
  class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Modify pending order by ticket                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool PendingOrderModify(const class="type">ulong ticket,const class="type">class="kw">double price_set,const class="type">class="kw">double sl,const class="type">class="kw">double tp)
  {
  ResetLastError();
  if(!OrderSelect((class="type">int)ticket,SELECT_BY_TICKET))

订单修改与按钮下单的容错逻辑

这段代码片段展示了 MT5 面板里修改挂单和响应按钮事件的核心判断。修改前先校验订单类型落在 ORDER_TYPE_SELL 到 ORDER_TYPE_SELL_STOP 区间,否则直接返回 false 并打印多语言报错,避免把持仓类型误当挂单处理。 若 OrderCloseTime() 大于 0,说明选中的是已删除订单,修改动作会被拦截并返回 false。颜色按类型区分:小于 ORDER_TYPE_SELL_LIMIT 的用 clrBlue,其余用 clrRed,方便在终端里一眼分辨买卖挂单。 按钮事件里用 StringSubstr 去掉前缀拿到纯 ID,ButtonState 为真才执行。按 BUTT_BUY 时先经 CorrectStopLoss / CorrectTakeProfit 相对 StopLevel 矫正价格,再走 trade.Buy(lot,SYMBOL(),0,sl,tp)(MQL5)或 Buy(lot,SYMBOL(),magic_number,sl,tp)(MQL4)开仓。外汇与贵金属杠杆高,挂单距离若不满足券商 StopLevel 会被拒,改参数前建议先 Print 出实际返回值验证。

MQL5 / C++
  {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
   }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
   {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((class="type">ENUM_POSITION_TYPE)type)," #",ticket);
      class="kw">return false;
   }
   if(OrderCloseTime()>class="num">0)
   {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      class="kw">return false;
   }
   class="type">class="kw">color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((class="type">int)ticket,price_set,sl,tp,class="num">0,clr))
   {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(class="type">class="kw">string)GetLastError());
      class="kw">return false;
   }
   class="kw">return true;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#ifdef __MQL5__
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle pressing the buttons                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PressButtonEvents(const class="type">class="kw">string button_name)
  {
   class=class="str">"cmt">//--- Convert the button name into its class="type">class="kw">string ID
   class="type">class="kw">string button=StringSubstr(button_name,StringLen(prefix));
   class=class="str">"cmt">//--- If the button is pressed
   if(ButtonState(button_name))
     {
      class=class="str">"cmt">//--- If the BUTT_BUY button is pressed: Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         class=class="str">"cmt">//--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         class="type">class="kw">double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,class="num">0,stoploss);
         class="type">class="kw">double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,class="num">0,takeprofit);
         class=class="str">"cmt">//--- Open Buy position
         class="macro">#ifdef __MQL5__
            trade.Buy(lot,Symbol(),class="num">0,sl,tp);
         class="macro">#else
            Buy(lot,Symbol(),magic_number,sl,tp);
         class="macro">#endif
        }
      class=class="str">"cmt">//--- If the BUTT_BUY_LIMIT button is pressed: Set BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {

「挂单价格与止损止盈的合规校正」

在 MT5 下发 BuyLimit 前,必须先按经纪商 StopLevel 约束把挂单价、止损价、止盈价全部校正一遍,否则订单会直接被拒。上面这段代码用 CorrectPricePending 算挂单价,再用 CorrectStopLoss / CorrectTakeProfit 以挂单价为基础反推 SL/TP,三层价格全部脱离裸参数。 MQL5 与 MQL4 的发送接口在此分叉:MQL5 走 trade.BuyLimit(lot,price_set,Symbol(),sl,tp),MQL4 则带 magic_number 参数走 BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp)。用 #ifdef __MQL5__ 做编译期隔离,一份源码两边都能编。 引擎基类 CEngine 里挂了 m_is_tester 这种标记位,回测环境下它会切到无延迟路径,实盘却要老老实实走事件队列。外汇与贵金属杠杆高,StopLevel 在重大数据行情可能瞬间扩到平时 3~5 倍,校正函数返回值必须做二次校验再发单。

MQL5 / C++
  class=class="str">"cmt">//--- Get the correct order placement price relative to StopLevel
  class="type">class="kw">double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
  class=class="str">"cmt">//--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
  class="type">class="kw">double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
  class="type">class="kw">double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
  class=class="str">"cmt">//--- Set BuyLimit order
  class="macro">#ifdef __MQL5__
    trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
  class="macro">#else
    BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp);
  class="macro">#endif
  }
class=class="str">"cmt">//--- If the BUTT_BUY_STOP button is pressed: Set BuyStop
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Library basis class                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CEngine : class="kw">public CObject
  {
class="kw">private:
  CHistoryCollection   m_history;             class=class="str">"cmt">// Collection of historical orders and deals
  CMarketCollection    m_market;              class=class="str">"cmt">// Collection of market orders and deals
  CEventsCollection    m_events;              class=class="str">"cmt">// Collection of events
  CArrayObj            m_list_counters;       class=class="str">"cmt">// List of timer counters
  class="type">bool                 m_first_start;         class=class="str">"cmt">// First launch flag
  class="type">bool                 m_is_hedge;            class=class="str">"cmt">// Hedge account flag
  class="type">bool                 m_is_tester;           class=class="str">"cmt">// Flag of working in the tester
  class="type">bool                 m_is_market_trade_event; class=class="str">"cmt">// Flag of an account trading event
  class="type">bool                 m_is_history_trade_event; class=class="str">"cmt">// Flag of an account history trading event
  ENUM_TRADE_EVENT     m_acc_trade_event;     class=class="str">"cmt">// Account trading event
class=class="str">"cmt">//--- Return counter index by id
class="kw">public:
  class=class="str">"cmt">//--- Return the list of market(class="num">1) positions, (class="num">2) pending orders and(class="num">3) market orders
  CArrayObj*           GetListMarketPosition(class="type">void);
  CArrayObj*           GetListMarketPendings(class="type">void);

◍ 引擎收尾的账户状态与计时器初始化

CEngine 的公开接口在尾部给出了几类查询与生命周期方法:GetListMarketOrders、GetListHistoryOrders 等返回 CArrayObj*,分别拿到当前市价单、历史订单、已删挂单、成交与按持仓 ID 归并的全部订单列表;ResetLastTradeEvent 与 LastTradeEvent 用来清空或读取最近一次交易事件枚举。 IsHedge 与 IsTester 是两个常被策略分支依赖的布尔量。前者在 MQL5 下直接读 ACCOUNT_MARGIN_MODE 是否等于 RETAIL_HEDGING,后者用 MQLInfoInteger(MQL_TESTER) 判定是否跑在回测环境——这两值在构造时定一次,后续不用反复查终端。 构造函数里先对 m_list_counters 做 Sort 和 Clear,再以 CreateCounter(COLLECTION_COUNTER_ID, COLLECTION_COUNTER_STEP, COLLECTION_PAUSE) 建一个采集计数器;m_is_tester 在构造期赋值,意味着EA在 OnInit 之前就已知自己是否在 tester 中。 开 MT5 把这段塞进你自己的 CEngine 派生类,断点打在构造函数末尾,能看到 m_is_hedge 在零售对冲账户上返回 true,而 m_is_tester 在普通图表加载时为 false、在策略测试器里为 true。外汇与贵金属杠杆品种波动剧烈,回测通过不代表实盘能复现,账户模式判断错可能让平仓逻辑直接走偏。

MQL5 / C++
CArrayObj* GetListMarketOrders(class="type">void);
class=class="str">"cmt">//--- Return the list of historical(class="num">1) orders, (class="num">2) removed pending orders, (class="num">3) deals, (class="num">4) all position market orders by its id
CArrayObj* GetListHistoryOrders(class="type">void);
CArrayObj* GetListHistoryPendings(class="type">void);
CArrayObj* GetListDeals(class="type">void);
CArrayObj* GetListAllOrdersByPosID(const class="type">ulong position_id);
class=class="str">"cmt">//--- Reset the last trading event
class="type">void ResetLastTradeEvent(class="type">void) { this.m_events.ResetLastTradeEvent(); }
class=class="str">"cmt">//--- Return the(class="num">1) last trading event, (class="num">2) hedge account flag, (class="num">3) flag of working in the tester
ENUM_TRADE_EVENT LastTradeEvent(class="type">void) const { class="kw">return this.m_acc_trade_event; }
class="type">bool IsHedge(class="type">void) const { class="kw">return this.m_is_hedge; }
class="type">bool IsTester(class="type">void) const { class="kw">return this.m_is_tester; }
class=class="str">"cmt">//--- Create the timer counter
class="type">void CreateCounter(const class="type">int id,const class="type">ulong frequency,const class="type">ulong pause);
class=class="str">"cmt">//--- Timer
class="type">void OnTimer(class="type">void);
class=class="str">"cmt">//--- Constructor/destructor
CEngine();
~CEngine();
};
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
  this.m_list_counters.Sort();
  this.m_list_counters.Clear();
  this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
  this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif;
  this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
  ::ResetLastError();
  class="macro">#ifdef __MQL5__
让小布替你跑这套兼容检查
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到跨版本编译冲突的提示,把重复劳动交给小布,你专注决策。

常见问题

MQL4 标准库不含 CTrade 类,需要用条件编译指令 #ifdef __MQL5__ 把相关对象实例隔离,MT4 分支另写等效逻辑。
MQL4 保留了单参数旧式调用,而 MQL5 只用图表 ID 打头的三参数形式,传 0 时编译器无法判定该走哪条重载,需显式区分。
可以,小布盯盘内置的 AIGC 能标记跨版本编译冲突与事件定义缺失,省去手动逐条比对报错的过程。
建议先用上篇的测试 EA 在 MT4 编译,按首个报错顺藤摸瓜,优先补交易类包含与条件编译分支。
需在函数库层抽象出事件枚举,用条件编译映射各自底层回调,使上层 EA 不感知终端版本差。