本文介绍的是使用编程方法追踪MetaTrader 4客户终端中的事件, 它的目标读者是对终端的操作和MQL4编程具有基本知识和技能的人员.
为了实现某些策略, 在EA交易中只知道是否已有仓位是不够的. 有些时候需要"抓住" 建立/关闭/修改仓位或者触发挂单的瞬间. 在MQL4中, 没有能够解决此问题的已经封装好的函数, 但是用于创建此类工具的材料都是有的. 这就是我们将要做的.
怎样才能知道某个事件发生了呢?一般来说, 什么是事件?为了回答此类问题, 我们将作如下定义: 事件就是某个订单/已开仓位状态的改变. 对应着我们的任务目标, 例子就是, 修改已建仓位的数量或者某些仓位的止损水平.
怎样才能侦测到某时刻有事件发生呢?非常简单. 为此, 需要记下将要跟踪的数值(我们的例子中, 是仓位的数量), 然后, 例如在下一个时刻, 把它与新获得的值作比较. 让我们写一个简单的EA交易用于通知我们仓位数量的改变.
int start() { static bool first = true; static int pre_OrdersTotal = 0; int _OrdersTotal = OrdersTotal(); // 如果这是第一次运行此EA交易, 我们不知道前一时刻订单的数量. // 所以只需要记录它, 标记已经进行了第一次运行, 并退出. if ( first ) { pre_OrdersTotal = _OrdersTotal; first = false; return(0); } // 比较前一时刻的仓位数量和当前时刻的数量 // 如果发生改变则显示信息 if ( _OrdersTotal > pre_OrdersTotal ) Alert( "仓位数量增加!之前数量为 - ", pre_OrdersTotal, ", 现有数量为 - ", _OrdersTotal ); if ( _OrdersTotal < pre_OrdersTotal ) Alert( "仓位数量减少!!之前数量为 - ", pre_OrdersTotal, ", 现有数量为 - ", _OrdersTotal ); // 记录仓位数量 pre_OrdersTotal = _OrdersTotal; return(0); }
有必要注意此EA交易中的特别之处:
最后一个问题可以通过在start函数体中进行循环来解决. 这样, 检查不是在每一个订单时刻进行, 而是每隔一定时间进行:
int start() { static bool first = true; static int pre_OrdersTotal = 0; int _OrdersTotal = OrdersTotal(); // 如果这是第一次运行EA交易, 我们不知道前一时刻的订单数量. // 所以我们将只是记录它, 标记第一次运行已经发生, 再退出. if ( first ) { pre_OrdersTotal = _OrdersTotal; first = false; return(0); } while ( !IsStopped() ) { _OrdersTotal = OrdersTotal(); // 比较前一时刻的仓位数量和当前时刻的数量. // 如果发生改变了, 则显示信息 if ( _OrdersTotal > pre_OrdersTotal ) Alert( "仓位数量增加!之前数量为 - ", pre_OrdersTotal, ", 现有数量为 - ", _OrdersTotal ); if ( _OrdersTotal < pre_OrdersTotal ) Alert( "仓位数量减少!!之前数量为 - ", pre_OrdersTotal, ", 现有数量为 - ", _OrdersTotal ); // 记录仓位数量 pre_OrdersTotal = _OrdersTotal; Sleep(100); } return(0); }
在以上版本中, 仓位数量改变的信息会立即出现, 您可以试试看!
在现在的实现中, 我们的EA交易能够在所有交易品种建立新的仓位时通知我们. 但是通常情况下我们只是需要知道当前交易品种中订单数量的改变信息i. 另外, 通过EA交易管理的订单通常用幻数(MagicNumber)做标记. 让我们使用这两个条件过滤数据. 也就是说, 我们只在当前交易品种和订单幻数为MagicNumber的情况下通知改变.
extern int MagicNumber = 0; int start() { static bool first = true; static int pre_OrdersTotal = 0; int _OrdersTotal = 0, now_OrdersTotal = 0, _GetLastError = 0; while ( !IsStopped() ) { _OrdersTotal = OrdersTotal(); now_OrdersTotal = 0; for ( int z = _OrdersTotal - 1; z >= 0; z -- ) { if ( !OrderSelect( z, SELECT_BY_POS ) ) { _GetLastError = GetLastError(); Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - 错误 #", _GetLastError ); continue; } // 根据当前交易品种和指定的幻数MagicNumber进行订单计数 if ( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() ) now_OrdersTotal ++; } // 只有不是第一次运行时才显示数据 if ( !first ) { // 比较当前时刻和前一时刻的订单数量 // 如果有所改变, 显示信息 if ( now_OrdersTotal > pre_OrdersTotal ) Alert( Symbol(), ": 幻数为 ", MagicNumber, " 的仓位数量增加!之前数量 - ", pre_OrdersTotal, ", 现有数量 - ", now_OrdersTotal ); if ( now_OrdersTotal < pre_OrdersTotal ) Alert( Symbol(), ": 幻数为 ", MagicNumber, " 的仓位数量下降!之前数量 - ", pre_OrdersTotal, ", 现有数量- ", now_OrdersTotal ); } else { first = false; } //---- 记住仓位数量 pre_OrdersTotal = now_OrdersTotal; Sleep(100); } return(0); }
得到订单的总数当然很好, 但是有些时候需要更加详细的信息 - 例如, "是否建立了一个买入或者卖出仓位?", "是否有挂单被触发?", "平仓是止损, 获利还是人工平仓的?". 让我们尝试做一个需要跟踪的事件列表, 做得更加完善并且把它们分组.
在我们实现算法之前, 让我们检查一下是否以上所列的所有事件都有必要. 如果您需要创建一个对所有仓位的任何变化都要通知或者报告给我们的EA交易, 答案就是"是", 所有这些事件都需要考虑. 但是我们的目标简单一些: 我们希望的是EA交易"理解"它所处理的仓位有什么事情发生. 在这种情况下, 列表可以显著变短: 建立仓位, 下挂单, 修改内容和人工关闭仓位可以从列表中删除 - 这些事件是由EA本身产生的(没有EA交易就不会发生). 这样, 我们就得到了如下列表:
现在, 列表看起来没那么可怕了, 让我们开始写代码吧. 要记得有些保留的是有几种方法定义关闭仓位的方法(止损, 获利):
第一种方法实现起来简单一些, 不过可能会产生错误数据 - 如果有两个仓位在同一时刻内关闭, 一个人工关闭另外一个止损关闭, EA交易就会产生两个相同的事件, 而只发现最后关闭的仓位(如果最后关闭的仓位是人工关闭, 两个事件都会认为是人工关闭). EA交易并不"知道"其中一个仓位是因为止损关闭的.
所以, 为了避免这样的麻烦, 让我们尽量把代码写完善.
extern int MagicNumber = 0; // 前一时刻开启仓位的数组 int pre_OrdersArray[][2]; // [仓位数][订单编号 #, 仓位类型] int start() { // 首次运行标志 static bool first = true; // 最新错误代码 int _GetLastError = 0; // 仓位总数 int _OrdersTotal = 0; // 仓位总数达到要求 (当前交易品种和幻数), // 在当前时刻 int now_OrdersTotal = 0; // 仓位总数达到要求(当前交易品种和指定幻数), // 在前一时刻 static int pre_OrdersTotal = 0; // 当前时刻的开启仓位数组 int now_OrdersArray[][2]; // [# 列表中的编号][订单编号 #, 仓位类型] // 数组中现有仓位的数量 now_OrdersArray (用于搜索) int now_CurOrder = 0; // 前一时刻数组中仓位的数量 pre_OrdersArray (用于搜索) int pre_CurOrder = 0; // 用于保存每种类型关闭仓位数量的数组 int now_ClosedOrdersArray[6][3]; // [订单类型][关闭类型] // 用于保存触发挂单数量的数组 int now_OpenedPendingOrders[4]; // [订单类型] (一共只有四种挂单类型) // 临时标志 bool OrderClosed = true, PendingOrderOpened = false; // 临时变量 int ticket = 0, type = -1, close_type = -1; //+------------------------------------------------------------------+ //| 无限循环 //+------------------------------------------------------------------+ while ( !IsStopped() ) { // 记住仓位总数 _OrdersTotal = OrdersTotal(); // 把开启仓位数组大小改成现有数量 ArrayResize( now_OrdersArray, _OrdersTotal ); // 数组清零 ArrayInitialize( now_OrdersArray, 0.0 ); // 把满足要求的仓位数清零 now_OrdersTotal = 0; // 关闭仓位和触发订单的数组清零 ArrayInitialize( now_ClosedOrdersArray, 0.0 ); ArrayInitialize( now_OpenedPendingOrders, 0.0 ); //+------------------------------------------------------------------+ //| 寻找仓位, 并且在数组中写下 //| 满足条件的仓位 //+------------------------------------------------------------------+ for ( int z = _OrdersTotal - 1; z >= 0; z -- ) { if ( !OrderSelect( z, SELECT_BY_POS ) ) { _GetLastError = GetLastError(); Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - 错误 #", _GetLastError ); continue; } // 为当前交易品种和指定幻数的订单计数 if ( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() ) { now_OrdersArray[now_OrdersTotal][0] = OrderTicket(); now_OrdersArray[now_OrdersTotal][1] = OrderType(); now_OrdersTotal ++; } } // 把开启仓位数组大小改为满足条件的仓位数 ArrayResize( now_OrdersArray, now_OrdersTotal ); //+------------------------------------------------------------------+ //| 在前一时刻数组中搜索订单号和数量 //| 看多少个仓位被关闭, 多少挂单被触发 //+------------------------------------------------------------------+ for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ ) { // 记住订单编号和订单类型 ticket = pre_OrdersArray[pre_CurOrder][0]; type = pre_OrdersArray[pre_CurOrder][1]; // 认定仓位被关闭 OrderClosed = true; // 认定挂单未被触发 PendingOrderOpened = false; // 在开启仓位列表中搜索 for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ ) { // 如果在列表中找到此编号的仓位, if ( ticket == now_OrdersArray[now_CurOrder][0] ) { // 仓位未被关闭 (订单未被取消) OrderClosed = false; // 如果类型改变, if ( type != now_OrdersArray[now_CurOrder][1] ) { // 挂单被触发 PendingOrderOpened = true; } break; } } // 如果仓位未被关闭 (订单未被取消), if ( OrderClosed ) { // 选择订单 if ( !OrderSelect( ticket, SELECT_BY_TICKET ) ) { _GetLastError = GetLastError(); Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - 错误 #", _GetLastError ); continue; } // 检查仓位是怎样关闭的 (订单取消): if ( type < 2 ) { // 买入和卖出: 0 - 人工, 1 - 止损, 2 - 获利 close_type = 0; if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1; if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2; } else { // 挂单: 0 - 人工, 1 - 过期 close_type = 0; if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1; } // 把订单类型写进关闭订单数组 // 以 close_type 方式取消 now_ClosedOrdersArray[type][close_type] ++; continue; } // 挂单被触发, if ( PendingOrderOpened ) { // 把触发订单类型写进触发挂单数组 now_OpenedPendingOrders[type-2] ++; continue; } } //+------------------------------------------------------------------+ //| 收集了所有所需信息 - 显示它 //+------------------------------------------------------------------+ // 如果不是第一次运行EA交易 if ( !first ) { // 在触发挂单数组中搜索全部元素 for ( type = 2; type < 6; type ++ ) { // 如果元素不为空 (有该类型订单被触发), 显示信息 if ( now_OpenedPendingOrders[type-2] > 0 ) Alert( Symbol(), ": 被触发 ", _OrderType_str( type ), " 订单!" ); } // 在已关闭订单数组中搜索全部元素 for ( type = 0; type < 6; type ++ ) { for ( close_type = 0; close_type < 3; close_type ++ ) { // 如果元素不为空 (有仓位被关闭), 显示信息 if ( now_ClosedOrdersArray[type][close_type] > 0 ) CloseAlert( type, close_type ); } } } else { first = false; } //---- 把当前仓位数组信息保存到前期仓位数组中 ArrayResize( pre_OrdersArray, now_OrdersTotal ); for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ ) { pre_OrdersArray[now_CurOrder][0] = now_OrdersArray[now_CurOrder][0]; pre_OrdersArray[now_CurOrder][1] = now_OrdersArray[now_CurOrder][1]; } pre_OrdersTotal = now_OrdersTotal; Sleep(100); } return(0); } void CloseAlert( int alert_type, int alert_close_type ) { string action = ""; if ( alert_type < 2 ) { switch ( alert_close_type ) { case 1: action = " 通过止损!"; break; case 2: action = " 通过获利!"; break; default: action = " 人工!"; break; } Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-仓位关闭", action ); } else { switch ( alert_close_type ) { case 1: action = " 由于过期!"; break; default: action = " 人工!"; break; } Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-订单取消", action ); } } // 以文本方式返回 OrderType string _OrderType_str( int _OrderType ) { switch ( _OrderType ) { case OP_BUY: return("买入"); case OP_SELL: return("卖出"); case OP_BUYLIMIT: return("限价买入"); case OP_BUYSTOP: return("止损买入"); case OP_SELLLIMIT: return("限价卖出"); case OP_SELLSTOP: return("止损卖出"); default: return("未知订单类型"); } }
为了更方便地从任何EA交易中使用"事件陷阱"功能, 让我们把代码放到Events.mq4中以便其他EA交易从#include目录中包含它. 为此:
这就是我们应该得到的结果:
// 前一时刻的开启仓位数组 int pre_OrdersArray[][2]; // [仓位数量][订单编号 #, 仓位类型] // 事件变量 int eventBuyClosed_SL = 0, eventBuyClosed_TP = 0; int eventSellClosed_SL = 0, eventSellClosed_TP = 0; int eventBuyLimitDeleted_Exp = 0, eventBuyStopDeleted_Exp = 0; int eventSellLimitDeleted_Exp = 0, eventSellStopDeleted_Exp = 0; int eventBuyLimitOpened = 0, eventBuyStopOpened = 0; int eventSellLimitOpened = 0, eventSellStopOpened = 0; void CheckEvents( int magic = 0 ) { // 第一次运行标记 static bool first = true; // 最新错误代码 int _GetLastError = 0; // 仓位总数 int _OrdersTotal = OrdersTotal(); // 满足标准的仓位数量 (当前交易品种以及指定的幻数), // 当前订单时刻 int now_OrdersTotal = 0; // 前一订单时刻中满足条件的仓位数量 static int pre_OrdersTotal = 0; // 当前时刻开启仓位的数组 int now_OrdersArray[][2]; // [# 列表中的编号][订单编号 #, 仓位类型] // now_OrdersArray 数组中仓位的当前编号(为了搜索) int now_CurOrder = 0; // pre_OrdersArray 数组中仓位的当前编号(为了搜索) int pre_CurOrder = 0; // 用于保存每种类型关闭仓位数量的数组 int now_ClosedOrdersArray[6][3]; // [订单类型][关闭类型] // 用于保存触发挂单数量的数组 int now_OpenedPendingOrders[4]; // [订单类型] // 临时标志 bool OrderClosed = true, PendingOrderOpened = false; // 临时变量 int ticket = 0, type = -1, close_type = -1; //事件变量清零 eventBuyClosed_SL = 0; eventBuyClosed_TP = 0; eventSellClosed_SL = 0; eventSellClosed_TP = 0; eventBuyLimitDeleted_Exp = 0; eventBuyStopDeleted_Exp = 0; eventSellLimitDeleted_Exp = 0; eventSellStopDeleted_Exp = 0; eventBuyLimitOpened = 0; eventBuyStopOpened = 0; eventSellLimitOpened = 0; eventSellStopOpened = 0; // 把开启仓位数组大小改成现有数量 ArrayResize( now_OrdersArray, MathMax( _OrdersTotal, 1 ) ); // 数组清零 ArrayInitialize( now_OrdersArray, 0.0 ); // 把关闭仓位和触发挂单的数组清零 ArrayInitialize( now_ClosedOrdersArray, 0.0 ); ArrayInitialize( now_OpenedPendingOrders, 0.0 ); //+------------------------------------------------------------------+ //| 搜索全部仓位并把 //| 符合条件的写入数组 //+------------------------------------------------------------------+ for ( int z = _OrdersTotal - 1; z >= 0; z -- ) { if ( !OrderSelect( z, SELECT_BY_POS ) ) { _GetLastError = GetLastError(); Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - 错误 #", _GetLastError ); continue; } // 为当前交易品种和指定幻数的订单进行计数 if ( OrderMagicNumber() == magic && OrderSymbol() == Symbol() ) { now_OrdersArray[now_OrdersTotal][0] = OrderTicket(); now_OrdersArray[now_OrdersTotal][1] = OrderType(); now_OrdersTotal ++; } } // 把开启仓位数组的大小改为满足条件仓位的数量 ArrayResize( now_OrdersArray, MathMax( now_OrdersTotal, 1 ) ); //+-------------------------------------------------------------------------------------------------+ //| 在前一订单时刻仓位列表中搜索并计数有多少仓位被关闭 //| 和挂单被触发 //+-------------------------------------------------------------------------------------------------+ for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ ) { // 记录订单编号和订单类型 ticket = pre_OrdersArray[pre_CurOrder][0]; type = pre_OrdersArray[pre_CurOrder][1]; // 假定一个仓位被关闭 OrderClosed = true; // 假定一个挂单没有被触发 PendingOrderOpened = false; // 在开启仓位列表中搜索 for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ ) { // 如果列表中有这个订单编号的仓位, if ( ticket == now_OrdersArray[now_CurOrder][0] ) { // 这表示仓位还没有被关闭 (订单尚未被取消) OrderClosed = false; // 如果类型改变, if ( type != now_OrdersArray[now_CurOrder][1] ) { // 表示这是一个挂单且被触发 PendingOrderOpened = true; } break; } } // 如果仓位被关闭 (订单已经被取消), if ( OrderClosed ) { // 选择订单 if ( !OrderSelect( ticket, SELECT_BY_TICKET ) ) { _GetLastError = GetLastError(); Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - 错误 #", _GetLastError ); continue; } // 检查仓位是怎样关闭的 (订单取消): if ( type < 2 ) { // 买入和卖出: 0 - 人工, 1 - 止损, 2 - 获利 close_type = 0; if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1; if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2; } else { // 挂单: 0 - 人工, 1 - 过期 close_type = 0; if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1; } // 并写入到已关闭订单数组的'type'类型中 // 关闭类型是 close_type now_ClosedOrdersArray[type][close_type] ++; continue; } // 挂单被触发, if ( PendingOrderOpened ) { // 把订单类型为'type'的被触发的挂单写入触发挂单数组 now_OpenedPendingOrders[type-2] ++; continue; } } //+--------------------------------------------------------------------------------------------------+ //| 全部所需信息已经收集完毕 - 把所需数值赋值给事件变量 //+--------------------------------------------------------------------------------------------------+ // 如果不是第一次运行EA交易 if ( !first ) { // 在触发挂单数组中搜索全部元素 for ( type = 2; type < 6; type ++ ) { // 如果元素不为空 (此类型挂单没有被触发), 修改变量值 if ( now_OpenedPendingOrders[type-2] > 0 ) SetOpenEvent( type ); } // 在已关闭订单数组中搜索全部元素 for ( type = 0; type < 6; type ++ ) { for ( close_type = 0; close_type < 3; close_type ++ ) { // 如果元素不为空 (有仓位被关闭), 修改变量值 if ( now_ClosedOrdersArray[type][close_type] > 0 ) SetCloseEvent( type, close_type ); } } } else { first = false; } //---- 把当前仓位数组信息保存到前期仓位数组中 ArrayResize( pre_OrdersArray, MathMax( now_OrdersTotal, 1 ) ); for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ ) { pre_OrdersArray[now_CurOrder][0] = now_OrdersArray[now_CurOrder][0]; pre_OrdersArray[now_CurOrder][1] = now_OrdersArray[now_CurOrder][1]; } pre_OrdersTotal = now_OrdersTotal; } void SetOpenEvent( int SetOpenEvent_type ) { switch ( SetOpenEvent_type ) { case OP_BUYLIMIT: eventBuyLimitOpened ++; return(0); case OP_BUYSTOP: eventBuyStopOpened ++; return(0); case OP_SELLLIMIT: eventSellLimitOpened ++; return(0); case OP_SELLSTOP: eventSellStopOpened ++; return(0); } } void SetCloseEvent( int SetCloseEvent_type, int SetCloseEvent_close_type ) { switch ( SetCloseEvent_type ) { case OP_BUY: { if ( SetCloseEvent_close_type == 1 ) eventBuyClosed_SL ++; if ( SetCloseEvent_close_type == 2 ) eventBuyClosed_TP ++; return(0); } case OP_SELL: { if ( SetCloseEvent_close_type == 1 ) eventSellClosed_SL ++; if ( SetCloseEvent_close_type == 2 ) eventSellClosed_TP ++; return(0); } case OP_BUYLIMIT: { if ( SetCloseEvent_close_type == 1 ) eventBuyLimitDeleted_Exp ++; return(0); } case OP_BUYSTOP: { if ( SetCloseEvent_close_type == 1 ) eventBuyStopDeleted_Exp ++; return(0); } case OP_SELLLIMIT: { if ( SetCloseEvent_close_type == 1 ) eventSellLimitDeleted_Exp ++; return(0); } case OP_SELLSTOP: { if ( SetCloseEvent_close_type == 1 ) eventSellStopDeleted_Exp ++; return(0); } } }
现在只要EA交易使用该库就可以从中跟踪事件了. 以下是一个EA交易的例子 (EventsExpert.mq4):
extern int MagicNumber = 0; #include <Events.mq4> int start() { CheckEvents( MagicNumber ); if ( eventBuyClosed_SL > 0 ) Alert( Symbol(), ": 买入仓位由于止损被关闭!" ); if ( eventBuyClosed_TP > 0 ) Alert( Symbol(), ": 买入仓位由于获利被关闭!" ); if ( eventBuyLimitOpened > 0 || eventBuyStopOpened > 0 || eventSellLimitOpened > 0 || eventSellStopOpened > 0 ) Alert( Symbol(), ": 挂单被触发!" ); return(0); }
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程