简介
本文主要研究OCO类型订单的处理。此机制已在MetaTrader 5的一些竞争产品中实施。通过这个用控制面板处理OCO订单的例子,我想实现两个目标。首先,我要介绍标准类库的特性。另一方面,我想扩大交易员的交易工具。
1。OCO命令的性质
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。“类型”下拉列表提供订单类型。
如果我们使用枚举订单类型标准枚举类型,我们可以将其设置为市场订单类型(订单类型购买或订单类型出售),但我们不需要只处理提单。
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。“购买停止”和“购买停止限制”订单对
以下信息将反映在专家中:
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订单的面板:初始状态
我们需要选择一个订单类型并设置相关参数来放置OCO订单对。
然后面板上唯一按钮的标签将被更改(文本属性,图5)。
图5。创建OCO订单面板:新订单对
标准类库中的类用于构建面板:
- CappDialog是主应用程序对话框:
- CPanel是一个矩形标签:
- Clabel是文本标签:
- cComboBox是一个下拉列表框:
- CEdit是输入框:
- cButton是按钮。
当然,panel类的方法是自动调用的。
现在让我们看看代码。我必须提到,用于创建指示面板和对话框的标准库非常大。
例如,如果要捕获下拉列表关闭事件,则必须深入堆栈内部调用它们。
图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。
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。