轻松快捷开发 MetaTrader 程序的函数库(第十部分):与 MQL4 的兼容性 - 开仓和激活挂单的事件·进阶篇
(2/3)· 从 34 个编译错误出发,把 MQL4 缺失的交易类与事件定义逐一补齐
按订单号平仓的两个底层函数
在 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 按品种波动调一调。
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_SELL | type>ORDER_TYPE_SELL_STOP 来拦掉市价持仓。外汇与贵金属杠杆高,这类“删单失败静默返回”的代码若不加警报,实盘可能倾向在你不知情时保留危险敞口。 |
|---|
开 MT5 把这段塞进自己的脚本,故意传一个已平仓 ticket,应该立刻在专家日志看到 'Position already closed' 或俄文版,证明守卫逻辑生效。
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 日志是否如预期输出错误码。
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 出实际返回值验证。
{
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 倍,校正函数返回值必须做二次校验再发单。
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。外汇与贵金属杠杆品种波动剧烈,回测通过不代表实盘能复现,账户模式判断错可能让平仓逻辑直接走偏。
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__