跨平台智能交易程序: 订单·综合运用
(3/3)·从例程到扩展,把 MT4、MT5 净持与对冲的订单逻辑收拢进一套可复用框架
很多跨平台 EA 作者在 MT4 部分平仓后丢单号、在 MT5 净持里分不清订单与仓位,导致逻辑在换平台时直接崩。本文作为系列收尾,把前两层铺垫过的约定落到例程与扩展,让一套类对象在三种模式里都能跑通。
用 COrder 实例接住开仓细节
想在 MT5 里开一笔多头并把成交上下文留存下来,核心是把交易对象、订单容器、品种对象三者绑好。EA 的 OnTick 中调 trade.Buy 之后,订单号用 trade.ResultOrder() 拿,再靠 HistoryOrderSelect 把历史订单里的 ticket、magic、symbol、volume、open price 捞出来塞进 COrders.NewOrder。 MQL4 那边交易对象能力弱一些,没有对称的 HistoryOrderSelect 体系,只能循环 OrdersTotal 按 magic=12345 过滤活跃订单来补同样的细节。两个版本最终都只跑一次 OnTick,随后 ExpertRemove 把自己从图表摘掉。 实跑日志可作验证锚点:MT5 中 EURUSD,M1 下成交价 1.10669、偏差 10 点、订单 #90114599 在 275 ms 内 done;MT4 中 EURUSD,H1 成交价 1.12470、订单 #358063536。注意 MT5 的成交记录写在终端「日志」栏,不在 EA 栏。 下面这段是 MQL5 恢复订单细节与 MT4 回退逻辑的拼接示例,直接看逐行更好懂:
<span class="keyword">class="type">class="kw">ulong</span> retcode=trade.ResultRetcode(); <span class="keyword">class="type">class="kw">ulong</span> order = trade.ResultOrder(); i<span class="keyword">f</span>(retcode==<span class="macro">TRADE_RETCODE_DONE</span>) { <span class="keyword">if</span>(<span class="functions">HistoryOrderSelect</span>(order)) { <span class="keyword">class="type">class="kw">ulong</span> ticket=<span class="functions">HistoryOrderGetInteger</span>(order,<span class="macro">ORDER_TICKET</span>); <span class="keyword">class="type">class="kw">ulong</span> magic=<span class="functions">HistoryOrderGetInteger</span>(order,<span class="macro">ORDER_MAGIC</span>); <span class="keyword">class="type">class="kw">string</span> symbol = <span class="functions">HistoryOrderGetString</span>(order,<span class="macro">ORDER_SYMBOL</span>); <span class="keyword">class="type">class="kw">double</span> volume = <span class="functions">HistoryOrderGetDouble</span>(order,<span class="macro">ORDER_VOLUME_INITIAL</span>); <span class="keyword">class="type">class="kw">double</span> price=<span class="functions">HistoryOrderGetDouble</span>(order,<span class="macro">ORDER_PRICE_OPEN</span>); <span class="macro">ENUM_ORDER_TYPE</span> order_type=(<span class="macro">ENUM_ORDER_TYPE</span>)<span class="functions">HistoryOrderGetInteger</span>(order,<span class="macro">ORDER_TYPE</span>); orders.NewOrder((<span class="keyword">class="type">int</span>)ticket,symbol,(<span class="keyword">class="type">int</span>)magic,order_type,volume,price); } } <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i<<span class="functions">OrdersTotal</span>();i++) { <span class="keyword">if</span>(!<span class="functions">OrderSelect</span>(i,<span class="macro">SELECT_BY_POS</span>)) <span class="keyword">class="kw">continue</span>; <span class="keyword">if</span>(<span class="functions">OrderMagicNumber</span>()==<span class="number">class="num">12345</span>) orders.NewOrder(<span class="functions">OrderTicket</span>(),<span class="functions">OrderSymbol</span>(),<span class="functions">OrderMagicNumber</span>(),(<span class="macro">ENUM_ORDER_TYPE</span>)<span class="functions">OrderType</span>(),<span class="functions">OrderLots</span>(),<span class="functions">OrderOpenPrice</span>()); } <span class="preprocessor">class="macro">#include </span><MQLx-Orders\Base\Trade\ExpertTradeXBase.mqh> <span class="preprocessor">class="macro">#include </span><MQLx-Orders\Base\Order\OrdersBase.mqh> CExpertTradeX trade; COrders orders; CSymbolInfo symbolinfo; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 智能程序初始化函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> <span class="functions">OnInit</span>() { <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">if</span>(!symbolinfo.Name(<span class="functions">Symbol</span>())) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"品种初始化失败"</span>); <span class="keyword">class="kw">return</span> <span class="macro">INIT_FAILED</span>; } trade.SetSymbol(<span class="functions">GetPointer</span>(symbolinfo)); trade.SetExpertMagicNumber(<span class="number">class="num">12345</span>); <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 智能程序逆初函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnDeinit</span>(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> reason) {
「下单后如何把成交单塞进自己的容器」
在 OnTick 里先调用 RefreshRates 刷新品种报价,失败就打印“品种不能刷新”并直接 return,避免用过期价格下单。外汇与贵金属杠杆高,报价断裂时硬跑交易函数可能吃到滑点,这条守卫不能省。 若 trade.Buy(1.0, Ask(), 0, 0) 返回 true,MQL5 分支下要取回结果码与订单号:ResultRetCode 等于 TRADE_RETCODE_DONE 才用 HistoryOrderSelect 选中历史订单,逐个 HistoryOrderGetInteger / GetString / GetDouble 把 ticket、magic、symbol、volume、price 读出来,再交给 m_orders.NewOrder 登记。 MQL4 兼容分支走 OrdersTotal 循环,用 OrderSelect 按位选取,过滤 MagicNumber==12345 后调 orders.NewOrder 写库。两个分支本质都是把经纪商回执转成本地可追踪结构。 下单成功后 Sleep(5000) 再 ExpertRemove,EA 只做一次市价买 1.0 手就自我卸载。想验证就把代码贴进 MT5 脚本,把 magic 改成自己常用的数,看日志里 HistoryOrderGetInteger 取到的 ticket 是否和终端“交易”标签一致。
class="type">void OnTick() { class=class="str">"cmt">//--- if(!symbolinfo.RefreshRates()) { Print("品种不能刷新"); class="kw">return; } if(trade.Buy(class="num">1.0,symbolinfo.Ask(),class="num">0,class="num">0)) { class="macro">#ifdef __MQL5__ class="type">int retcode=trade.ResultRetCode(); class="type">class="kw">ulong order = trade.ResultOrder(); if(retcode==TRADE_RETCODE_DONE) { if(HistoryOrderSelect(order)) { class="type">class="kw">ulong ticket=HistoryOrderGetInteger(order,ORDER_TICKET);; class="type">class="kw">ulong magic=HistoryOrderGetInteger(order,ORDER_MAGIC); class="type">class="kw">string symbol = HistoryOrderGetString(order,ORDER_SYMBOL); class="type">class="kw">double volume = HistoryOrderGetDouble(order,ORDER_VOLUME_INITIAL); class="type">class="kw">double price=HistoryOrderGetDouble(order,ORDER_PRICE_OPEN); ENUM_ORDER_TYPE order_type=order_type; m_orders.NewOrder((class="type">int)ticket,symbol,(class="type">int)magic,order_type,volume,price); } } class="macro">#else for(class="type">int i=class="num">0;i<OrdersTotal();i++) { if(!OrderSelect(i,SELECT_BY_POS)) class="kw">continue; if(OrderMagicNumber()==class="num">12345) orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),(ENUM_ORDER_TYPE)OrderType(),OrderLots(),OrderOpenPrice()); } class="macro">#endif } Sleep(class="num">5000); ExpertRemove(); }
◍ 当前实现的三个现实短板
眼下这套 EA 骨架离实盘还有三道坎。第一是开仓时不会顺手挂上初始止损和止盈,只能裸奔进场;第二是没有修改止损止盈的机制,盈亏平衡、尾随止损或自定义移动止损都无从谈起。 第三是数据只活在内存里。MT4 与 MT5 对订单和停止位的持久化方式本就不同,我们的类对象在两个版本中结构一致,但终端一重启或切换图表就全丢,必须靠外部存储把订单信息落盘并在启动时回填。 以上三点会在后续篇章逐个补上,当前版本不建议直接上真实账户——外汇与贵金属杠杆高,未挂止损的仓位遇跳空可能瞬间扩大浮亏。
记住这一条就够了
把已成交请求封成一个 COrder 实例,跨平台 EA 就能在同一对象上持续做平仓、改止盈止损等后续动作,而不必每次都重新查历史。基础模板只是起点,真要跑复杂策略还得自己扩字段和方法。 上面那段历史订单遍历代码,从尾到头扫 OrdersHistory,用 CheckPointer 挡空指针,再用 IsClosed 和 OrderType 过滤出已平掉的买卖单(类型 0 或 1)。直接抄进 MT5 脚本,把 order_manager 换成你自己的管理器就能跑。 外汇和贵金属杠杆高、滑点猛,部分成交和超时在真实账户里都可能砸过来,类封装省事但别当它替你管了风险。
COrders *orders=order_manager.OrdersHistory(); for(class="type">int i=order_manager.OrdersHistoryTotal()-class="num">1;i>=class="num">0;i--) { COrder *order=orders.At(i); if(!CheckPointer(order)) class="kw">continue; order.OnTick(); if(!order.IsClosed()) class="kw">continue; if(order.OrderType()!=class="num">0 && order.OrderType()!=class="num">1) class="kw">continue;