跨平台智能交易程序: 订单·综合运用
🔁

跨平台智能交易程序: 订单·综合运用

(3/3)·从例程到扩展,把 MT4、MT5 净持与对冲的订单逻辑收拢进一套可复用框架

新手友好 第 3/3 篇

很多跨平台 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 回退逻辑的拼接示例,直接看逐行更好懂:

MQL5 / C++
<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>)
&nbsp;{
&nbsp; <span class="keyword">if</span>(<span class="functions">HistoryOrderSelect</span>(order))
&nbsp; {
&nbsp;  <span class="keyword">class="type">class="kw">ulong</span> ticket=<span class="functions">HistoryOrderGetInteger</span>(order,<span class="macro">ORDER_TICKET</span>);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">ulong</span> magic=<span class="functions">HistoryOrderGetInteger</span>(order,<span class="macro">ORDER_MAGIC</span>);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span> symbol = <span class="functions">HistoryOrderGetString</span>(order,<span class="macro">ORDER_SYMBOL</span>);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> volume = <span class="functions">HistoryOrderGetDouble</span>(order,<span class="macro">ORDER_VOLUME_INITIAL</span>);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> price=<span class="functions">HistoryOrderGetDouble</span>(order,<span class="macro">ORDER_PRICE_OPEN</span>);
&nbsp;&nbsp; <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>);
&nbsp;&nbsp; orders.NewOrder((<span class="keyword">class="type">int</span>)ticket,symbol,(<span class="keyword">class="type">int</span>)magic,order_type,volume,price);
&nbsp; }
&nbsp;}
<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>;i&lt;<span class="functions">OrdersTotal</span>();i++)
{
<span class="keyword">if</span>(!<span class="functions">OrderSelect</span>(i,<span class="macro">SELECT_BY_POS</span>))
&nbsp; <span class="keyword">class="kw">continue</span>;
&nbsp;<span class="keyword">if</span>(<span class="functions">OrderMagicNumber</span>()==<span class="number">class="num">12345</span>)
&nbsp; 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>&lt;MQLx-Orders\Base\Trade\ExpertTradeXBase.mqh&gt;
<span class="preprocessor">class="macro">#include </span>&lt;MQLx-Orders\Base\Order\OrdersBase.mqh&gt;
CExpertTradeX trade;
COrders orders;
CSymbolInfo symbolinfo;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 智能程序初始化函数             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!symbolinfo.Name(<span class="functions">Symbol</span>()))
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"品种初始化失败"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">INIT_FAILED</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; trade.SetSymbol(<span class="functions">GetPointer</span>(symbolinfo));
&nbsp;&nbsp; trade.SetExpertMagicNumber(<span class="number">class="num">12345</span>);
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="macro">INIT_SUCCEEDED</span>);
&nbsp;&nbsp;}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 智能程序逆初函数               &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</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)
&nbsp;&nbsp;{

「下单后如何把成交单塞进自己的容器」

在 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 是否和终端“交易”标签一致。

MQL5 / C++
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 换成你自己的管理器就能跑。 外汇和贵金属杠杆高、滑点猛,部分成交和超时在真实账户里都可能砸过来,类封装省事但别当它替你管了风险。

MQL5 / C++
  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;
把跨平台订单诊断交给小布
这些订单与仓位映射关系,小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前平台模式下的单号流转状态,你专注策略而非底层差异。

常见问题

MT4 的 OrderClose 只返回布尔值,剩余手数会被服务器生成新单号,只能重扫活跃订单列表拿新识别符,无法从函数直接取。
可以,小布盯盘的品种页会标注平台模式并展示订单、成交、仓位三类令牌的当前状态,省去你手动读配置。
不是,订单是交易请求或挂单,成交是单笔执行结果,仓位才是服务器合并后的净持头寸,三者令牌类型不同。
MT4 挂单直接对应订单概念,MT5 净持与对冲都需区分订单与成交,对冲还保留独立订单号,例程里建议统一用容器封装。