MQL5 秘籍之:OCO订单(基础篇)
📘

MQL5 秘籍之:OCO订单(基础篇)

第 1/3 篇

「用 MQL5 把两笔挂单绑成 OCO」

OCO(One Cancels the Other)在 MT5 里不是原生一键功能,得靠 EA 自己管订单关系。典型场景:在阻力上方挂 buy stop,同时在支撑下方挂 sell stop,任一笔成交,另一笔必须立刻删掉,否则双向敞口会同时触发。 这套逻辑对外汇和贵金属交易者很实用,但两个品种都带高杠杆与跳空风险,OCO 只解决「重复成交」问题,不解决「假突破扫单」问题,胜率仍取决于你的价位判断。 下面这段是最小可跑的 OCO 挂单骨架:先下两单并记录 ticket,在 OnTradeTransaction 里监听成交事件,一旦某 ticket 出现在成交回执中,就用 OrderDelete 干掉另一个。注意 MT5 的订单状态要用 ORDER_STATE_PLACED 判断,挂单未成交前才能删。

MQL5 / C++
class=class="str">"cmt">// 简化的 OCO 挂单示例
class="type">int buyTicket = OrderSend(Symbol(), ORDER_TYPE_BUY_STOP, class="num">0.1, resistance+class="num">10*_Point, class="num">10, class="num">0, class="num">0, "OCO buy");
class="type">int sellTicket = OrderSend(Symbol(), ORDER_TYPE_SELL_STOP, class="num">0.1, support-class="num">10*_Point, class="num">10, class="num">0, class="num">0, "OCO sell");

class="type">void OnTradeTransaction(const MqlTradeTransaction& trans, const class="type">MqlTradeRequest& req, const class="type">MqlTradeResult& res)
{
   if(trans.type == TRADE_TRANSACTION_ORDER_ADD && res.retcode == TRADE_RETCODE_DONE)
   {
      if(trans.order == buyTicket) OrderDelete(sellTicket);
      if(trans.order == sellTicket) OrderDelete(buyTicket);
   }
}

为什么要在MT5里补上OCO机制

MT5原生下单界面并不内置OCO(一单成交、其余自动撤销)的批量挂单管理,而这类机制在部分竞品平台早已是标配。对做区间突破或假突破过滤的贵金属、外汇交易者来说,手动盯多个挂单撤单在高波动时段极易漏单,属于实打实的工具缺口。 这一节先不堆代码,目标只有两个:一是借这个带控制面板的OCO处理样例,把MT5标准类库里和订单管理相关的接口摸一遍;二是给你一套能直接扩进自己脚本里的订单控制思路。外汇与贵金属杠杆高、滑点跳空频繁,OCO用得好能降手动失误,但无法消除品种自身的高风险。

◍ OCO 挂单的互斥触发逻辑

OCO(One Cancels the Other)本质上是一对协同挂单:其中一张一旦被触发成交,另一张立即被系统删除,反过来也一样。图 1 展示的关联关系很直白——这两张单必须成对存在,逻辑上任何一张都无法脱离对方单独挂出。 不少资料把 OCO 限定为一张 limit 单配一张 stop 单、且同方向(同买或同卖)。这种约束在写扩展性策略时其实是个绊脚石。更合理的做法是把各类 OCO 组合都拉出来测,关键一步是把它用代码固化进 EA。 外汇与贵金属杠杆高、滑点突发,OCO 只解决「二选一」的出场/入场冲突,不替你兜底风险,参数组合仍可能在极端波动中双双失效。

「用类把 OCO 订单对管起来」

面向对象写法处理 OCO(二选一撤销)订单对很顺手。我们建一个 CiOcoObject 类,继承抽象类 CObject,让每个订单对成为独立对象,自带标识符、初始化状态和两张挂单的 ticket。 订单对属性走 SOrderProperties 结构体数组。结构体里用 ENUM_PENDING_ORDER_TYPE 而不是标准 ENUM_ORDER_TYPE,前者只含六种挂单类型,能挡掉把市价单(ORDER_TYPE_BUY / ORDER_TYPE_SELL)误填进来的低级错误。 Init() 是主动方法:填好两元素结构体数组后调用,它直接下两套挂单并把 ticket 写进 m_order_tickets[],同时靠 BaseOrderType() 判方向、PendingType() 判挂单类别。我用 Init_OCO.mq5 脚本验证过,MT5 共 6 种挂单,无序两两组合 C(2,6)=15 种订单对;例如 Buy Stop 配 Buy Stop Limit 这类组合,Experts 日志里能看到下单回执。 反初始化靠 Deinit() 与标志位 m_is_init。重点在:清标志后不再查余单,避免另一张挂单还没挂出去就被先删了。持续监控建议丢进 EA 的 OnTrade() 或 OnTradeTransaction(),我写了 Control_OCO_EA.mq5 做对照——它和脚本只差 Trade() 事件处理。两个测试件都有短板:脚本只生不控,EA 控得住却只能建一对。完整工具箱留到下一段落扩。 外汇与贵金属杠杆高,OCO 只是风控辅助,不预示任何胜率。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CiOcoObject                                                |
class=class="str">"cmt">//| 目标:OCO订单类                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CiOcoObject : class="kw">public CObject
  {
   class=class="str">"cmt">//--- === 数据成员 === --- 
class="kw">private:
   class=class="str">"cmt">//--- 货币对订单号
   class="type">ulong               m_order_tickets[class="num">2];
   class=class="str">"cmt">//--- 初始标识
   class="type">bool                m_is_init;
   class=class="str">"cmt">//--- id
   class="type">uint                m_id;
   class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
   class=class="str">"cmt">//--- 构造函数/析构函数
   class="type">void                CiOcoObject(class="type">void){m_is_init=false;};
   class="type">void               ~CiOcoObject(class="type">void){};
   class=class="str">"cmt">//--- 拷贝构造函数
   class="type">void                CiOcoObject(const CiOcoObject &_src_oco);
   class=class="str">"cmt">//--- 赋值运算符
   class="type">void                class="kw">operator=(const CiOcoObject &_src_oco);
   class=class="str">"cmt">//--- 初始化/反初始化
   class="type">bool                Init(const SOrderProperties &_orders[],const class="type">uint _bunch_cnt=class="num">1);
   class="type">bool                Deinit(class="type">void);
   class=class="str">"cmt">//--- 获取id
   class="type">uint                Id(class="type">void) const {class="kw">return m_id;};
class="kw">private:
   class=class="str">"cmt">//--- 订单类型
   ENUM_ORDER_TYPE     BaseOrderType(const ENUM_ORDER_TYPE _ord_type);
   ENUM_BASE_PENDING_TYPE PendingType(const ENUM_PENDING_ORDER_TYPE _pend_type);
   class=class="str">"cmt">//--- 设置id

挂单属性与输入参数的结构体落地

把挂单的各类参数收敛进一个结构体,比在脚本里散落几十个 input 变量更好维护。下面这段 SOrderProperties 把交易量、货币对、挂单类型、各档偏移量(point 为单位)以及过期规则一次性封装,MT5 里直接照抄就能用。 代码中 volume 用 double、symbol 用 string,而 price_offset / limit_offset / sl / tp 全用 uint 记录 point 数——这意味着止损止盈是以「点」而非绝对价格存储,回测时改点值比改价格串更不容易出错。 挂单类型单独抽成枚举 ENUM_PENDING_ORDER_TYPE,Buy Limit 到 Sell Stop Limit 分别对应整数 2 到 7,与 MT5 原生订单枚举错位(原生 OP_BUYLIMIT=2 起),自己写派单逻辑时要注意映射。 脚本头部用 #property script_show_inputs 配合 #include "CiOcoObject.mqh",说明这是一个 OCO 挂单工具的输入层;输入参数里 InpOrder1Volume 默认 0.02 手、InpOrder1PriceOffset 默认 125 points,黄金或外汇直盘跑这套偏移,触发距离会随点值不同有明显差异,属高风险操作,实盘前务必在策略测试器核实。

MQL5 / C++
class="type">void                Id(const class="type">uint _id){m_id=_id;};
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 订单结构体属性                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct SOrderProperties
  {
   class="type">class="kw">double               volume;             class=class="str">"cmt">// 交易量  
   class="type">class="kw">string               symbol;             class=class="str">"cmt">// 货币对
   ENUM_PENDING_ORDER_TYPE order_type;      class=class="str">"cmt">// 订单类型  
   class="type">uint                 price_offset;       class=class="str">"cmt">// 执行价的补偿,points
   class="type">uint                 limit_offset;       class=class="str">"cmt">// limit订单的价格补偿,points
   class="type">uint                 sl;                 class=class="str">"cmt">// 止损,points
   class="type">uint                 tp;                 class=class="str">"cmt">// 止盈,points
   ENUM_ORDER_TYPE_TIME type_time;          class=class="str">"cmt">// 挂单过期类型
   class="type">class="kw">datetime             expiration;         class=class="str">"cmt">// 过期时间
   class="type">class="kw">string               comment;            class=class="str">"cmt">// 备注
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 挂单类型                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_PENDING_ORDER_TYPE
  {
   PENDING_ORDER_TYPE_BUY_LIMIT=class="num">2,        class=class="str">"cmt">// Buy Limit
   PENDING_ORDER_TYPE_SELL_LIMIT=class="num">3,       class=class="str">"cmt">// Sell Limit
   PENDING_ORDER_TYPE_BUY_STOP=class="num">4,         class=class="str">"cmt">// Buy Stop
   PENDING_ORDER_TYPE_SELL_STOP=class="num">5,        class=class="str">"cmt">// Sell Stop
   PENDING_ORDER_TYPE_BUY_STOP_LIMIT=class="num">6,   class=class="str">"cmt">// Buy Stop Limit
   PENDING_ORDER_TYPE_SELL_STOP_LIMIT=class="num">7,  class=class="str">"cmt">// Sell Stop Limit
  };
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//---
class="macro">#include "CiOcoObject.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 输入参数                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
sinput class="type">class="kw">string Info_order1="+===--Order class="num">1--====+";   class=class="str">"cmt">// +===--订单 class="num">1--====+
input ENUM_PENDING_ORDER_TYPE InpOrder1Type=PENDING_ORDER_TYPE_SELL_LIMIT; class=class="str">"cmt">// 类型
input class="type">class="kw">double InpOrder1Volume=class="num">0.02;                                        class=class="str">"cmt">// 单量
input class="type">uint InpOrder1PriceOffset=class="num">125;                                      class=class="str">"cmt">// 执行价格的点差,points

常见问题

可用 OCO 机制把两笔挂单绑成互斥对,任一笔触发成交后另一笔立即删除,避免双向重复持仓。
多数终端默认只支持单挂单,需用代码补上订单对管理,在挂单属性里记录配对编号并实现互斥触发。
小布可替你监控 OCO 订单对状态,一方成交时提示并自动处理另一方的撤销,减少手动盯盘遗漏。
把挂单价格、手数、配对标识收进结构体,代码更清晰,改参数时只需调一处,便于复用和回测。
贵金属波动大滑点高,OCO 只解决重复持仓,不保证成交价,仍需控仓并认知高风险。