下载MetaTrader 5

MetaTrader 4 中的事件

13 一月 2016, 08:48
Andrey Khatimlianskii
0
1 372

简介

本文介绍的是使用编程方法追踪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交易中的特别之处:

  • firstpre_OrdersTotal 变量被定义为static(静态). 这样, 当离开start()函数后, 它们的数值不会被清零. 静态变量也可以用全局变量做替代(在函数之外做声明), 但是外部变量过多可能引起命名混乱 (有可能在函数内部错误声明同名的变量以至于引起冲突). 所以我们将所有的变量定义都包含在函数体之内.
  • 此EA交易在已开订单和挂单数量改变时都会发出通知(OrdersTotal()函数返回它们的总和).
  • 此EA交易在挂单被触发时并不会发出通知, 因为在那种情况下 OrdersTotal() 的值不会改变.
  • EA交易在第一次运行开始时不能侦测订单数量的改变, 因为它并不'知道'在前一时刻有多少订单.
  • 在运行EA交易的图表中, 只有在交易品种有新时刻来临时才会显示信息. 此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);
}


优化

得到订单的总数当然很好, 但是有些时候需要更加详细的信息 - 例如, "是否建立了一个买入或者卖出仓位?", "是否有挂单被触发?", "平仓是止损, 获利还是人工平仓的?". 让我们尝试做一个需要跟踪的事件列表, 做得更加完善并且把它们分组.

  1. 建立一个仓位
    • "市场仓位"
      • 买入
      • 卖出
    • 挂单
      • 限价买入
      • 限价卖出
      • 止损买入
      • 止损卖出
  2. 触发订单
    • 限价买入
    • 限价卖出
    • 止损买入
    • 止损卖出
  3. 平掉一个仓位
    • "市场仓位"
      • 买入
        • 止损
        • 获利
        • 人工 (既不是止损也不是获利)
      • 卖出
        • 止损
        • 获利
        • 人工
    • 挂单 (删除)
      • 限价买入
        • 过期
        • 人工
      • 限价卖出
        • 过期时间
        • 人工
      • 止损买入
        • 过期
        • 人工
      • 止损卖出
        • 过期
        • 人工
  4. 修改一个仓位
    • "市场仓位"
      • 买入
        • 止损
        • 获利
      • 卖出
        • 止损
        • 获利
    • 挂单
      • 限价买入
        • 建仓价格
        • 止损
        • 获利
        • 过期
      • 限价卖出
        • 建仓价格
        • 止损
        • 获利
        • 过期
      • 止损买入
        • 建仓价格
        • 止损
        • 获利
        • 过期
      • 止损卖出
        • 建仓价格
        • 止损
        • 获利
        • 过期

在我们实现算法之前, 让我们检查一下是否以上所列的所有事件都有必要. 如果您需要创建一个对所有仓位的任何变化都要通知或者报告给我们的EA交易, 答案就是"是", 所有这些事件都需要考虑. 但是我们的目标简单一些: 我们希望的是EA交易"理解"它所处理的仓位有什么事情发生. 在这种情况下, 列表可以显著变短: 建立仓位, 下挂单, 修改内容和人工关闭仓位可以从列表中删除 - 这些事件是由EA本身产生的(没有EA交易就不会发生). 这样, 我们就得到了如下列表:

  1. 触发订单
    • 限价买入
    • 限价卖出
    • 止损买入
    • 止损卖出
  2. 平仓
    • "市场仓位"
      • 买入
        • 止损
        • 获利
      • 卖出
        • 止损
        • 获利
    • 挂单 (过期)
      • 限价买入
      • 限价卖出
      • 止损买入
      • 止损卖出

现在, 列表看起来没那么可怕了, 让我们开始写代码吧. 要记得有些保留的是有几种方法定义关闭仓位的方法(止损, 获利):

  • 如果总的仓位数下降了, 就在历史中搜索最近关闭的仓位再侦测其参数, 看它是怎样被关闭的, 或者
  • 记住所有开启仓位的订单号, 然后再在历史中搜索"消失"仓位的订单号.

第一种方法实现起来简单一些, 不过可能会产生错误数据 - 如果有两个仓位在同一时刻内关闭, 一个人工关闭另外一个止损关闭, 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交易中整合和使用

为了更方便地从任何EA交易中使用"事件陷阱"功能, 让我们把代码放到Events.mq4中以便其他EA交易从#include目录中包含它. 为此:

  • 把代码写成函数的形式以便EA其后的调用.
  • 把全局变量MagicNumber删除, 给函数增加一个magic参数(它们的作用相同, 我们这样做只是不想影响了EA的外部变量);
  • 为每个事件增加一个外部变量 – 这将使它们的使用更加方便 (我们必须在函数开始时把这些变量清零);
  • 删除无限循环 – 现在必须在函数调用之间'取样' (也就是说, 调用一次函数, 我们只是获得了和上次调用函数之间的变化列表);
  • 删除警报, 如有必要可以在EA交易中加上;
  • 根据以上这些变化整理代码.

这就是我们应该得到的结果:

// 前一时刻的开启仓位数组
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);
}


6. 结论

通过这篇文章, 我们考虑了在MetaTrader 4中使用MQL4语言以编程方式跟踪事件的方法. 我们把所有的事件分成3组, 并且根据预先定义的条件对它们进行过滤. 我们还创建了一个函数库, 从而可以在任何EA交易中很容易地跟踪某些事件.
可以完成CheckEvents() 函数(或者作为模板使用)来跟踪其它的事件, 但是本文没有做这些工作.

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1399

附加的文件 |
Events.mq4 (8.62 KB)
EventsExpert.mq4 (0.93 KB)
文件操作. 一个重要市场时间可视化的实例 文件操作. 一个重要市场时间可视化的实例

本文展示并展望了使用MQL4在外汇交易市场上做出更加高效的工作.

图形EA交易: AutoGraf 图形EA交易: AutoGraf

本文展示了使用图形创建一个用于管理交易的方便界面的可行性.

无缝图表 无缝图表

本文介绍了没有跳空柱形图表的实现.

创建自定义指标的特性 创建自定义指标的特性

在MetaTrader交易系统中创建自定义指标有一些特性.