English Русский 中文 Español Deutsch Português
МetaТrader 4のイベント

МetaТrader 4のイベント

MetaTrader 4 | 15 3月 2016, 07:57
2 129 0
Andrey Khatimlianskii
Andrey Khatimlianskii

概論

この記事は、注文のオープン、クローズ、変更などのイベントを観測するМetaТrader 4ターミナルのプログラムに着目しており、MQL4でのプログラミングやターミナルを扱う基本的なスキルを持つユーザーを想定して書かれています。



1. イベントとは?そして何故それを観測するのか?

いくつかの戦略を実装する為には、エキスパートアドバイザによって開かれたポジションがあるかどうかを知るだけでは十分ではありません。時には、オープンやクローズ、またはポジションの変更や指し値注文のトリガーの瞬間を捉える必要があります。

MQL4には、この課題を自力で解決する内蔵の機能はありませんが、このようなツールを作成する為に必要なものは全てあります。これについて取り組んでいきたいと思います。

2. イベント定義の原則

イベントが発生したことをどう知るか?そもそもイベントとは何なのか?これらの疑問に答えると、大まかに言って次の結論に至ります。イベントとは、注文や開いているポジションの状態の変化です。私達の課題におけるこの変化とは、例えば、開いているポジションの数、またはポジションのストップロスのレベルです。

どのように今イベントが起きたかどうかを定義するか?これはとても簡単です。この為には、観測している課題を記憶し(私達の例ではポジションの数)、次のティックなど次の瞬間に、これを新たに取得した値と比較します。ポジションの数の変化を私達に知らせてくれる簡単なエキスパートアドバイザを作成します。

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // もしこれが最初の起動である場合、私達は以前のティックでの注文数はわかりません。
    // したがって、単にこれを覚えておき、最初の起動がすでにあったとメモし、終了します。
    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);
}

このエキスパートアドバイザのいくつかの特徴に触れる必要があります。

  • first変数とpre_OrdersTotal変数は、static. として宣言されました。このようにすることで、これらの数値はstart()関数から出る時にゼロにリセットされません。静的変数の代わりとして、グローバル変数(関数外で宣言)がありますが、これらの多くは名前のを起こす可能性があります。(エラーによって、関数内に同じ名前の変数を宣言することができ、そうすると競合を起こすことがあります)したがって、私達は関数の本体内で全ての変数を宣言します。
  • エキスパートアドバイザは、開いているポジションや指値注文の数の変化を知らせます。(OrdersTotal()関数はこれらの総数を返します)
  • エキスパートアドバイザは、この場合OrdersTotal()の数値は変化しないので、指値注文の作動について知らせます。
  • 最初の起動時にエキスパートアドバイザは、前のティックでの数がわからないので、注文数の変化を判別する事ができません。
  • エキスパートアドバイザが動作しているチャート上の、取引ツールの新しいティックの取得においてのみメッセージは出現します。エキスパートアドバイザでは、他のイベントの起動はありません。

最後の問題は、サイクルにスタート関数体を配置することで解決することができます。このようにして、チェックは各ティックで行われるわけではなく、指定された時間間隔で行なわれます。

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // もしこれが最初の起動である場合、私達は以前のティックでの注文数はわかりません。
    // したがって、単にこれを覚えておき、最初の起動がすでにあったとメモし、終了します。
    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);
}

このバージョンでは、ポジション数の変化についてのメッセージは、瞬間的に出現します。これは確認いただけると思います。

3. イベントのフィルタリング:基準

今回の実装では、私達のエキスパートアドバイザは、全ての取引ツールでのポジションの出現を私達に知らせてくれます。しかし、多くの場合、不可欠な情報は、現在のツールでの注文についてのみです。また、エキスパートアドバイザの管理下にある注文は、多くの場合、『マジックナンバー(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 ) - Error #", _GetLastError );
                continue;
            }
            // 現在の通貨ペアと指定されたMagicNumberでの注文数を算出します
            if ( OrderMagicNumber() == MagicNumber && 
                  OrderSymbol() == Symbol() ) now_OrdersTotal ++;
        }
 
        // これがエキスパートアドバイザの最初の起動ではない場合のみ、情報を出力します
        if ( !first )
        {
            // 前のティックでのポジションの数と現在のポジションの数を比較します
            // もしこれが変わった場合、メッセージを出力します
            if ( now_OrdersTotal > pre_OrdersTotal ) 
                Alert( Symbol(), ": количество позиций с MagicNumber ", MagicNumber,
                       "増加しました!以前は", pre_OrdersTotal, ", стало - ", now_OrdersTotal );
 
            if ( now_OrdersTotal < pre_OrdersTotal )
                Alert( Symbol(), ": количество позиций с MagicNumber ", MagicNumber,
                         "減少しました!以前は", pre_OrdersTotal, ", стало - ", now_OrdersTotal );
        }
        else
        {
            first = false;
        }
        //---- ポジション数を記憶します
        pre_OrdersTotal = now_OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}


4. 具体化

注文総数は勿論良いものですが、多くの場合、より具体的な情報が必要になります。例えば、『BUYとSELLどちらのポジションが開かれていたか?』、『指し値注文は発動したか?』、『ストップロスやテイクプロフィット、または手動でポジションが閉じられたか?』などです。それでは、観測が必要なイベントのより完全なリストを作成し、それをグループごとに分けてみましょう。

  1. ポジションオープン
    • 『市場のポジション』
      • BUY
      • SELL
    • 指値注文
      • バイリミット
      • セルリミット
      • バイストップ
      • セルストップ
  2. 注文の作動
    • バイリミット
    • セルリミット
    • バイストップ
    • セルストップ
  3. ポジションクローズ
    • 『市場のポジション』
      • BUY
        • ストップロス
        • テイクプロフィット
        • 手動(ストップロスでもテイクプロフィットでもない)
      • SELL
        • ストップロス
        • テイクプロフィット
        • 手動
    • 指値注文(削除)
      • バイリミット
        • 有効時間
        • 手動
      • セルリミット
        • 有効時間
        • 手動
      • バイストップ
        • 有効時間
        • 手動
      • セルストップ
        • 有効時間
        • 手動
  4. ポジションの変更
    • 『市場のポジション』
      • BUY
        • ストップロス
        • テイクプロフィット
      • SELL
        • ストップロス
        • テイクプロフィット
    • 指値注文
      • バイリミット
        • 始値
        • ストップロス
        • テイクプロフィット
        • 有効時間
      • セルリミット
        • 始値
        • ストップロス
        • テイクプロフィット
        • 有効時間
      • バイストップ
        • 始値
        • ストップロス
        • テイクプロフィット
        • 有効時間
      • セルストップ
        • 始値
        • ストップロス
        • テイクプロフィット
        • 有効時間

アルゴリズムの実装前に、リストアップされた全てのイベントが必要かどうかを考えてみましょう。もし全てのポジションの全ての変化を知らせるエキスパートアドバイザを作成するという目的を自分で持ったとすると、答えとしては、全てのこれらのイベントが考慮されるべきです。しかし、私達の目的はもっと質素なもので、エキスパートアドバイザが動作しているポジションに何が起こっているかを、エキスパートアドバイザが理解するのを助けることが目的です。この場合、リストを大幅に短縮できます。ポジションオープン、指値注文の設置、修正の全ブロック、そして全ての『手動』のポジションクローズは除去することができます。これは、エキスパートアドバイザ自身が生成するイベントです(エキスパートアドバイザなしには起こり得ないものです)。そして、私達に得られるものは以下の通りです。

  1. 注文の作動
    • バイリミット
    • セルリミット
    • バイストップ
    • セルストップ
  2. ポジションクローズ
    • 『市場のポジション』
      • BUY
        • ストップロス
        • テイクプロフィット
      • SELL
        • ストップロス
        • テイクプロフィット
    • 指値注文(タイムリミット)
      • バイリミット
      • セルリミット
      • バイストップ
      • セルストップ

こうすると、リストは遥かに『恐ろしく』ないものになり、私達はコードの作成に取り掛かることができます。ポジションクローズの方法(ストップロス、テイクプロフィット)を定義する為に追加条件のみ設定しますが、いくつかのバリエーションがあります。

  • ポジション総数の減少時には、履歴の中で最も最近閉じられたポジションを探し、そのパラメータに基づいてポジションがどのように閉じられたかを定義します。
  • 全てのオープンポジションのチケットを記憶し、それからチケットごとに履歴の中で『消えた』ポジションを探します。

最初の方法は実装するのは簡単ですが、間違った情報を与える可能性があります。もし一つのティックで、『手動』でのものとストップロスでのものの二つのポジションが閉じる時、エキスパートアドバイザは直近のクローズを発見すると、二つの同一イベントを生成します。(もし最後のポジションが手動で閉じられた場合、両方のイベントは『手動』のクローズとなります)そうすると、エキスパートアドバイザはそのうちの一つがストップロスで閉じられたことを知り得ません。

このような問題を回避する為に、すぐさまできる限り精巧なコードを書くことにしましょう。

extern int MagicNumber = 0;
 
// 前のティックの状態のオープンポジションの配列
int pre_OrdersArray[][2]; // [ポジション数][チケットナンバー、ポジションタイプ]
 
int start()
{
    // 第一の起動のフラグ
    static bool first = true;
    // 最後のエラーのコード
    int _GetLastError = 0;
    // ポジション総数
    int _OrdersTotal = 0;
    // 前のティック上の基準(現在のツールと指定されたMagicNumber)を満たす
    // ポジション数
    int now_OrdersTotal = 0;
    // 前のティック上の基準(現在のツールと指定されたMagicNumber)を満たす
    // ポジション数
    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]; // [オーダータイプ] (全部で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 ) - Error #", _GetLastError );
                continue;
            }
            // 現在の通貨ペアと指定されたMagicNumberでの注文数を算出します
            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 ) - Error #", _GetLastError );
                    continue;
                }
                // そして、どのようにポジションが閉じたかを定義します(注文が削除された)
                if ( type < 2 )
                {
                    // BUYとSELL: 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の状況の時にtypeのタイプを持つ注文が閉じられたと 
                // クローズポジションの配列に書き込みます
                now_ClosedOrdersArray[type][close_type] ++;
                continue;
            }
            // もし指値注文が作動した場合、
            if ( PendingOrderOpened )
            {
                // typeのタイプの注文が作動したと、作動した注文の配列に書き込みます
                now_OpenedPendingOrders[type-2] ++;
                continue;
            }
        }
 
        //+------------------------------------------------------------------+
        //| 全ての必要な情報は収集しました。情報を出力します
        //+------------------------------------------------------------------+
        // もしこれがエキスパートアドバイザの最初の起動ではない場合
        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("Buy");
        case OP_SELL:         return("Sell");
        case OP_BUYLIMIT:     return("BuyLimit");
        case OP_BUYSTOP:      return("BuyStop");
        case OP_SELLLIMIT:    return("SellLimit");
        case OP_SELLSTOP:     return("SellStop");
        default:              return("UnknownOrderType");
    }
}


5. エキスパートアドバイザへの統合と使用

任意のエキスパートアドバイザからの『イベントキャッチャー』の使用をしやすくする為に、Events.mq4ファイルにコードを『運び出し』、あとで#includeディレクティブのエキスパートアドバイザに含めるだけにします。その為には、

  • エキスパートアドバイザから呼び出される関数としてコードを作成します。
  • MagicNumberの外部変数を削除し、magic関数のパラメータを追加します。(これらは同じ役割を果たします。私たちは単にエキスパートアドバイザの外部変数のリストを『汚さない』ようにしましょう)
  • 各イベントにグローバル変数を一つずつ追加します。これによって、使用が最大限に快適になります。(同様に、関数の始めにこれらの変数をゼロ化することを忘れないようにしましょう)
  • 無限ループを削除します。これで『測定』は関数の呼び出しの間に行われます。(つまり、関数を呼び出して、私たちは前の関数呼び出しとの比較の変化のリストをただ受け取ります)
  • アラートを削除しましょう。必要に応じて、アラートはエキスパートアドバイザに追加することができます。
  • 上記の全てに関わるコードを綺麗にします。

さて、以下のようなものが出来上がるはずです。

// 前のティックの状態のオープンポジションの配列
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();
    // 前のティック上の基準(現在のツールと指定されたMagicNumber)を満たす
    // ポジション数
    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 ) - Error #", _GetLastError );
            continue;
        }
        // 現在の通貨ペアと指定されたMagicNumberでの注文数を算出します
        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 ) - Error #", _GetLastError );
                continue;
            }
            // そして、どのようにポジションが閉じたかを定義します(注文が削除された)
            if ( type < 2 )
            {
                // BUYとSELL: 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の状況の時にtypeのタイプを持つ注文が閉じられたと 
            // クローズポジションの配列に書き込みます
            now_ClosedOrdersArray[type][close_type] ++;
            continue;
        }
        // もし指値注文が作動した場合、
        if ( PendingOrderOpened )
        {
            // typeのタイプの注文が作動したと、作動した注文の配列に書き込みます
            now_OpenedPendingOrders[type-2] ++;
            continue;
        }
    }
 
    //+------------------------------------------------------------------+
    //| 全ての必要な情報は収集しました。イベントの変数に必要な数値を代入します
    //+------------------------------------------------------------------+
    // もしこれがエキスパートアドバイザの最初の起動ではない場合
    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);
        }
    }
}

これで、ライブラリを有効にするだけで、イベントの観測をあらゆるエキスパートアドバイザから使用できます。このようなエキスパートアドバイザの例は以下の通りです。(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. まとめ

この記事では、MQL4言語を使用したМetaТrader 4でのイベントの観測プログラムの方法を検証し、これらをグループごとに分け、指定された基準に従ってフィルタリングしました。また、あらゆるエキスパートアドバイザからイベントの一部を追加の労力なしに観測することができるライブラリを作成しました。

CheckEvents()関数は、この記事で検証されていない他のイベントを観測する為に変更する(もしくはテンプレートとして使用する)ことができます。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1399

添付されたファイル |
Events.mq4 (14.85 KB)
EventsExpert.mq4 (1.79 KB)
初心者向け MQL4 言語はじめに 初心者向け MQL4 言語はじめに
この記事シリーズは、プログラミングのことは何も知らないが、最短の時間、最小の労力でできるだけ速く MQL4 言語を学びたいという願いを持つトレーダーを対象としています。みなさんが『オブジェクト指向』、『三次元配列』などのフレーズに恐れをいだいているなら、本稿はそんな人が必要とするものです。レッスンは最大に迅速な結果を出すために作成されています。そのうえ、情報は理解しやすいように提供されています。理論を深く掘り下げすぎることはしませんが、初回レッスンですでに実用的な効用を得ることでしょう。
MQL5 クックブック: 板情報の実装 MQL5 クックブック: 板情報の実装
この記事では、板情報(DOM)を利用する方法を示します。また、CMarketBookクラスの動作についても説明します。これは、MQL5クラスの標準ライブラリを拡張し、DOMを使用する際の便利な方法となります。
ヘッジ  Expert Advisor コーディングの基礎 ヘッジ Expert Advisor コーディングの基礎
ヘッジ Expert Advisor の例が本稿で提供されたばかりです。私は個人的に好むヘッジペアを選びます。EURJPY と GBPJPY です。それはつねに同じように変動し、ヘッジングオーダータイプの設定が簡単なものです。
ファイルの操作。重要な市場イベントの可視化例。 ファイルの操作。重要な市場イベントの可視化例。
この記事では、外国為替証拠金取引市場でのより生産的なMQL4の使用を検証します。