外汇EA编写教程:MQL5秘籍:OCO订单

简介

本文主要研究OCO类型订单的处理。此机制已在MetaTrader 5的一些竞争产品中实施。通过这个用控制面板处理OCO订单的例子,我想实现两个目标。首先,我要介绍标准类库的特性。另一方面,我想扩大交易员的交易工具。

1。OCO命令的性质

OCO订单(一个订单取消另一个订单)代表一对提单。

它们通过相互撤销机制协同工作:如果一个订单被激活,第二个订单将被删除,反之亦然。

图 1 一对OCO订单

图1一对OCO订单

图1显示了一个简单的顺序相关性。这意味着两个订单必须同时存在。根据逻辑关系,这对订单不能单独存在。

一些消息来源说,这对订单必须是限价订单和止损订单,并且订单必须朝着相同的方向(买入或卖出)。据我所知,这种限制不利于创建可扩展的交易策略。我建议应该分析各种OCO顺序对,更重要的是,我们应该对它们进行编程。

2。程序顺序对

在我看来,OOP工具箱非常适合编写与OCO订单相关的任务。

下一节将创建新的数据类型以实现我们的目标。首选ciocoobject类。

2.1。CIOCoobject类

因此,我们需要一些类来操作两个相关的命令。

我们基于抽象类cobject创建一个新对象。

新类别如下:

//+------------------------------------------------------------------+
//| Class CiOcoObject                                                |
//| 目标:OCO订单类                                                    |            
//+------------------------------------------------------------------+
class CiOcoObject : public CObject
  {
   //--- === 数据成员 === --- 
private:
   //--- 货币对订单号
   ulong             m_order_tickets[2];
   //--- 初始标识
   bool              m_is_init;
   //--- id
   uint              m_id;

   //--- === 方法 === --- 
public:
   //--- 构造函数/析构函数
   void              CiOcoObject(void){m_is_init=false;};
   void             ~CiOcoObject(void){};
   //--- 拷贝构造函数
   void              CiOcoObject(const CiOcoObject &_src_oco);
   //--- 赋值运算符
   void              operator=(const CiOcoObject &_src_oco);

   //--- 初始化/反初始化
   bool              Init(const SOrderProperties &_orders[],const uint _bunch_cnt=1);
   bool              Deinit(void);
   //--- 获取id
   uint              Id(void) const {return m_id;};

private:
   //--- 订单类型
   ENUM_ORDER_TYPE   BaseOrderType(const ENUM_ORDER_TYPE _ord_type);
   ENUM_BASE_PENDING_TYPE PendingType(const ENUM_PENDING_ORDER_TYPE _pend_type);
   //--- 设置id
   void              Id(const uint _id){m_id=_id;};
  };

每个OCO订单都有自己的标识符。它的值由随机数生成器(Crandom类的对象)设置。

接口中包含订单对的初始化和反初始化方法。第一个方法创建(初始化)订单对,第二个方法删除(销毁)订单对。

ciocoobject::init()方法接收sorderproperties结构数组类型作为参数。结构类型存储订单对的相关属性,例如OCO订单。

2.2 Sorderproperties结构

让我们看看前面提到的结构的内部。

//+------------------------------------------------------------------+
//| 订单结构体属性                                                     |
//+------------------------------------------------------------------+
struct SOrderProperties
  {
   double                  volume;           // 交易量   
   string                  symbol;           // 货币对
   ENUM_PENDING_ORDER_TYPE order_type;       // 订单类型   
   uint                    price_offset;     // 执行价的补偿,points
   uint                    limit_offset;     // limit订单的价格补偿,points
   uint                    sl;               // 止损,points
   uint                    tp;               // 止盈,points
   ENUM_ORDER_TYPE_TIME    type_time;        // 挂单过期类型
   datetime                expiration;       // 过期时间
   string                  comment;          // 备注
  }

为了实现初始化方法,我们必须首先填充一个包含两个元素的结构化实体数组。简单地说,我们必须告诉程序要下哪个命令。

枚举值枚举挂起的顺序类型枚举值类型在结构中使用:

//+------------------------------------------------------------------+
//| 挂单类型                                                          |
//+------------------------------------------------------------------+
enum ENUM_PENDING_ORDER_TYPE
  {
   PENDING_ORDER_TYPE_BUY_LIMIT=2,       // Buy Limit
   PENDING_ORDER_TYPE_SELL_LIMIT=3,      // Sell Limit
   PENDING_ORDER_TYPE_BUY_STOP=4,        // Buy Stop
   PENDING_ORDER_TYPE_SELL_STOP=5,       // Sell Stop
   PENDING_ORDER_TYPE_BUY_STOP_LIMIT=6,  // Buy Stop Limit
   PENDING_ORDER_TYPE_SELL_STOP_LIMIT=7, // Sell Stop Limit
  };

总的来说,它看起来与标准枚举枚举枚举订单类型变量相同,但只能选择提单。

在输入参数(图2)中选择适当的订单类型时,可以避免错误。

图 2. "Type"下拉列表中提供了订单类型。

图2。“类型”下拉列表提供订单类型。

如果我们使用枚举订单类型标准枚举类型,我们可以将其设置为市场订单类型(订单类型购买或订单类型出售),但我们不需要只处理提单。

2.3。初始化订单对

如上所述,ciocoobject::init()方法用于初始化订单对。

它用于下订单和记录新订单对的成功。这是一个活动方法,它执行自己的事务操作。我们也可以创造被动的方法。它将独立放置的两个现有挂起列表关联起来。

我将不提供此方法完成的代码。但我想提醒读者,计算所有价格(开盘价、止损价、止利价、限价)是非常重要的。事务类中的方法ctrade::orderopen()可以下订单。最后,我们需要考虑两件事情:订单的方向(买入或卖出)和相对于当前订单价格的执行价格(更高或更低)。

此方法调用一些私有方法:BaseOrderType()和Pendingtype()。一是确定指示的方向,二是确定提单的类型。

下订单后,订单号存储在m_order_tickets[]数组中。

我使用了一个简单的init-oco.mq5脚本来测试这个方法。

#property script_show_inputs
//---
#include "CiOcoObject.mqh"
//+------------------------------------------------------------------+
//| 输入参数                                                          |
//+------------------------------------------------------------------+
sinput string Info_order1="+===--Order 1--====+";   // +===--订单 1--====+
input ENUM_PENDING_ORDER_TYPE InpOrder1Type=PENDING_ORDER_TYPE_SELL_LIMIT; // 类型
input double InpOrder1Volume=0.02;                  // 单量
input uint InpOrder1PriceOffset=125;                // 执行价格的点差,points
input uint InpOrder1LimitOffset=50;                 // limit单的点差, points
input uint InpOrder1SL=250;                         // 止损, points
input uint InpOrder1TP=455;                         // 止盈, points
input string InpOrder1Comment="OCO Order 1";        // 备注
//---
sinput string Info_order2="+===--Order 2--====+";   // +===--订单 2--====+
input ENUM_PENDING_ORDER_TYPE InpOrder2Type=PENDING_ORDER_TYPE_SELL_STOP; // 类型
input double InpOrder2Volume=0.04;                  // 单量    
input uint InpOrder2PriceOffset=125;                // 执行价格的点差, points
input uint InpOrder2LimitOffset=50;                 // limit单的点差, points
input uint InpOrder2SL=275;                         // 止损, points
input uint InpOrder2TP=300;                         // 止盈, points
input string InpOrder2Comment="OCO Order 2";        // 备注

//--- 全局变量
CiOcoObject myOco;
SOrderProperties gOrdersProps[2];
//+------------------------------------------------------------------+
//| 脚本程序start函数                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 第一张订单的属性
   gOrdersProps[0].order_type=InpOrder1Type;
   gOrdersProps[0].volume=InpOrder1Volume;
   gOrdersProps[0].price_offset=InpOrder1PriceOffset;
   gOrdersProps[0].limit_offset=InpOrder1LimitOffset;
   gOrdersProps[0].sl=InpOrder1SL;
   gOrdersProps[0].tp=InpOrder1TP;
   gOrdersProps[0].comment=InpOrder1Comment;

//--- 第二张订单的属性
   gOrdersProps[1].order_type=InpOrder2Type;
   gOrdersProps[1].volume=InpOrder2Volume;
   gOrdersProps[1].price_offset=InpOrder2PriceOffset;
   gOrdersProps[1].limit_offset=InpOrder2LimitOffset;
   gOrdersProps[1].sl=InpOrder2SL;
   gOrdersProps[1].tp=InpOrder2TP;
   gOrdersProps[1].comment=InpOrder2Comment;

//--- 订单对初始化
   if(myOco.Init(gOrdersProps))
      PrintFormat("Id of new OCO pair: %I32u",myOco.Id());
   else
      Print("Error when placing OCO pair!");
  }

在这里,您可以设置订单对的各种属性。MetaTrader 5有六种不同类型的票据。

这样,将有15个订单对(组合)(如果订单对属于不同类型)。

C(k,n)=C(2,6)=15

脚本中的所有变量都已经过测试。我将给出一个购买-购买-购买-停止限制订单对的例子。

订单类型在脚本参数中指定(图3)。

图 3. "Buy Stop"订单和"Buy Stop Limit"订单组成的订单对

图3。“购买停止”和“购买停止限制”订单对

以下信息将反映在专家中:

QO      0       17:17:41.020    Init_OCO (GBPUSD.e,M15) Code of request result: 10009
JD      0       17:17:41.036    Init_OCO (GBPUSD.e,M15) New order ticket: 24190813
QL      0       17:17:41.286    Init_OCO (GBPUSD.e,M15) Code of request result: 10009
JH      0       17:17:41.286    Init_OCO (GBPUSD.e,M15) New order ticket: 24190814
MM      0       17:17:41.379    Init_OCO (GBPUSD.e,M15) Id of new OCO pair: 3782950319

但如果不执行循环操作,我们就无法通过脚本处理OCO订单。

2.4。订单对
反初始化

此方法用于控制订单对。当任何订单从激活订单列表中消失时,订单对将“失效”。

我认为这个方法应该在EA的ontrade()或ontradeTransaction()函数中实现。这样,EA就可以毫不延迟地处理任何订单对的激活。

//+------------------------------------------------------------------+
//| 订单对反初始化                                                     |
//+------------------------------------------------------------------+
bool CiOcoObject::Deinit(void)
  {
//--- 如果订单对已初始化
   if(this.m_is_init)
     {
      //---检查订单 
      for(int ord_idx=0;ord_idx<ArraySize(this.m_order_tickets);ord_idx++)
        {
         //--- 订单对的当前订单
         ulong curr_ord_ticket=this.m_order_tickets[ord_idx];
         //--- 另一个订单
         int other_ord_idx=!ord_idx;
         ulong other_ord_ticket=this.m_order_tickets[other_ord_idx];

         //---
         COrderInfo order_obj;

         //--- 如果没有当前订单
         if(!order_obj.Select(curr_ord_ticket))
           {
            PrintFormat("Order #%d is not found in active orders list.",curr_ord_ticket);
            //--- 尝试删除另一个订单                 
            if(order_obj.Select(other_ord_ticket))
              {
               CTrade trade_obj;
               //---
               if(trade_obj.OrderDelete(other_ord_ticket))
                  return true;
              }
           }
        }
     }
//---
   return false;
  }

让我提醒你一个细节。检查类方法中订单对的初始化标志。如果清除此标识,则不会检查订单。这样做是为了避免在正确放置另一个账单之前删除现有订单。

让我们在脚本中添加一些功能来处理下订单的情况。我们创建了control_oco_ea.mq5来测试ea。

一般来说,ea和script的区别仅在于trade()事件处理模块:

//+------------------------------------------------------------------+
//| 交易函数                                                          |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- OCO 订单反初始化
   if(myOco.Deinit())
     {
      Print("No more order pair!");
      //--- 清除订单对
      CiOcoObject new_oco;
      myOco=new_oco;
     }
  }


这段视频展示了他们如何在MetaTrader 5终端上工作。

然而,这两个测试程序都有缺陷。

第一个程序(脚本)只能创建订单对,但会失去对它们的控制。

虽然第二个程序(EA)可以控制订单对,但在创建第一个订单对之后,它不能重复创建其他订单对。为了构建一个功能完整的OCO订单程序(脚本),我们需要将它扩展到一个工具箱中,这个工具箱可以用来生成订单。在下一章中,我们将实现这一点。

三。控制电针

让我们在图表上创建一个OCO订单管理面板来放置订单并设置订单对的参数。

这是控制EA的一部分(图4)。源代码在panel_oco_ea.mq5中。

图 4. 创建OCO订单的面板:初始状态

图4。用于创建OCO订单的面板:初始状态

我们需要选择一个订单类型并设置相关参数来放置OCO订单对。

然后面板上唯一按钮的标签将被更改(文本属性,图5)。

Fig. 5. 创建OCO订单的面板:新的订单对

图5。创建OCO订单面板:新订单对

标准类库中的类用于构建面板:

  • CappDialog是主应用程序对话框:
  • CPanel是一个矩形标签:
  • Clabel是文本标签:
  • cComboBox是一个下拉列表框:
  • CEdit是输入框:
  • cButton是按钮。

当然,panel类的方法是自动调用的。

现在让我们看看代码。我必须提到,用于创建指示面板和对话框的标准库非常大。

例如,如果要捕获下拉列表关闭事件,则必须深入堆栈内部调用它们。

Fig. 6. 调用堆栈

图6。调用栈

开发人员为%mql5/include/controls/defines.mqh文件中的特定事件设置宏和声明。

我创建了一个自定义事件来创建OCO订单对。

#define ON_OCO (101) // OCO 订单对创建事件 


订单参数的设置和订单对的创建将在onChartEvent()函数体中完成。nbsp;

//+------------------------------------------------------------------+
//| ChartEvent函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- 在主对话框处理所有图表事件
   myDialog.ChartEvent(id,lparam,dparam,sparam);

//--- 下拉列表处理
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE)
     {
      //--- 如果是面板列表
      if(!StringCompare(StringSubstr(sparam,0,7),"myCombo"))
        {
         static ENUM_PENDING_ORDER_TYPE prev_vals[2];
         //--- 列表索引
         int combo_idx=(int)StringToInteger(StringSubstr(sparam,7,1))-1;

         ENUM_PENDING_ORDER_TYPE curr_val=(ENUM_PENDING_ORDER_TYPE)(myCombos[combo_idx].Value()+2);
         //--- 记录订单类型的变更
         if(prev_vals[combo_idx]!=curr_val)
           {
            prev_vals[combo_idx]=curr_val;
            gOrdersProps[combo_idx].order_type=curr_val;
           }
        }
     }

//--- 处理输入框
   else if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- 如果是面板的输入框
      if(!StringCompare(StringSubstr(sparam,0,6),"myEdit"))
        {
         //--- 查找对象
         for(int idx=0;idx<ArraySize(myEdits);idx++)
           {
            string curr_edit_obj_name=myEdits[idx].Name();
            long curr_edit_obj_id=myEdits[idx].Id();
            //--- 如果名称重合
            if(!StringCompare(sparam,curr_edit_obj_name))
              {
               //--- 获取当前值
               double value=StringToDouble(myEdits[idx].Text());
               //--- 定义gOrdersProps[]数组索引
               int order_num=(idx<gEditsHalfLen)?0:1;
               //--- 定义gOrdersProps结构体编号
               int jdx=idx;
               if(order_num)
                  jdx=idx-gEditsHalfLen;
               //--- 填充gOrdersProps结构体
               switch(jdx)
                 {
                  case 0: // 交易量
                    {
                     gOrdersProps[order_num].volume=value;
                     break;
                    }
                  case 1: // 执行
                    {
                     gOrdersProps[order_num].price_offset=(uint)value;
                     break;
                    }
                  case 2: // limit
                    {
                     gOrdersProps[order_num].limit_offset=(uint)value;
                     break;
                    }
                  case 3: // stop
                    {
                     gOrdersProps[order_num].sl=(uint)value;
                     break;
                    }
                  case 4: // 获利
                    {
                     gOrdersProps[order_num].tp=(uint)value;
                     break;
                    }
                 }
              }
           }
         //--- OCO 订单对创建标识
         bool is_to_fire_oco=true;
         //--- 检查结构体 
         for(int idx=0;idx<ArraySize(gOrdersProps);idx++)
           {
            //---  如果订单类型已设置 
            if(gOrdersProps[idx].order_type!=WRONG_VALUE)
               //---  如果交易量已设置  
               if(gOrdersProps[idx].volume!=WRONG_VALUE)
                  //---  如果市价单的入场点差已设置
                  if(gOrdersProps[idx].price_offset!=(uint)WRONG_VALUE)
                     //---  如果limit单的入场点差已设置
                     if(gOrdersProps[idx].limit_offset!=(uint)WRONG_VALUE)
                        //---  如果止损已设置
                        if(gOrdersProps[idx].sl!=(uint)WRONG_VALUE)
                           //---  如果止赢已设置
                           if(gOrdersProps[idx].tp!=(uint)WRONG_VALUE)
                              continue;

            //--- 清除OCO订单对的创建标识 
            is_to_fire_oco=false;
            break;
           }
         //--- 创建OCO订单对?
         if(is_to_fire_oco)
           {
            //--- 填充备注字段
            for(int ord_idx=0;ord_idx<ArraySize(gOrdersProps);ord_idx++)
               gOrdersProps[ord_idx].comment=StringFormat("OCO Order %d",ord_idx+1);
            //--- 改变按钮属性
            myButton.Text("New pair");
            myButton.Color(clrDarkBlue);
            myButton.ColorBackground(clrLightBlue);
            //--- 响应用户操作 
            myButton.Enable();
           }
        }
     }
//--- 点击按钮
   else if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- 如果是OCO订单对创建按钮
      if(!StringCompare(StringSubstr(sparam,0,6),"myFire"))
         //--- 响应用户操作
         if(myButton.IsEnabled())
           {
            //--- 触发OCO订单对创建事件
            EventChartCustom(0,ON_OCO,0,0.0,"OCO_fire");
            Print("Command to create new bunch has been received.");
           }
     }
//--- 处理新订单对初始化命令 
   else if(id==CHARTEVENT_CUSTOM+ON_OCO)
     {
      //--- OCO订单对初始化
      if(gOco.Init(gOrdersProps,gOcoList.Total()+1))
        {
         PrintFormat("Id of new OCO pair: %I32u",gOco.Id());
         //--- 复制
         CiOcoObject *ptr_new_oco=new CiOcoObject(gOco);
         if(CheckPointer(ptr_new_oco)==POINTER_DYNAMIC)
           {
            //--- 添加到列表
            int node_idx=gOcoList.Add(ptr_new_oco);
            if(node_idx>-1)
               PrintFormat("Total number of bunch: %d",gOcoList.Total());
            else
               PrintFormat("Error when adding OCO pair %I32u to list!",gOco.Id());
           }
        }
      else
         Print("OCO-orders placing error!");

      //--- 清除相关属性
      Reset();
     }
  }

处理器有很多代码。我想集中讨论几个模块。

首先,在主对话框中处理所有图表事件。

接下来是处理各种其他事件的模块:

  • 更改下拉列表以定义订单类型。
  • 编辑输入框以设置订单属性。
  • 单击按钮生成事件。
  • 关于事件响应:订单类型创建。

EA不验证面板中字段的正确性。这就是为什么我们必须自己检查这些值,否则当OCO订单失败时,EA会出错。

删除订单对和关闭剩余订单的操作在ontrade()处理函数中实现。

总结

我试图展示标准库的丰富性,它可以用来满足我们的一些特定需求。

特别地,我们使用它来解决OCO订单的处理问题。我希望这个带有OCO订单控制面板的EA将成为您创建更复杂订单对的起点。

本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/1582。

附加文件下载zip init_oco.mq5(3.17 kb)control_oco_ea.mq5(3.92 kb)panel_oco_ea.mq5(15.34 kb)ciocoobject。mqh(13.9kb)起重机。MQH(2.59 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投