デルタインジケータの例によるボリュームコントロールを特徴とする株式インジケータの開発

Tapochun | 25 10月, 2018

コンテンツ

イントロダクション

周知のように、MetaTrader5 は2種類のボリュームをブロードキャストしています。

ターミナルでは、実際のボリュームはVolumeとして示されます。 これは我々にとって興味深いものになるでしょう。 ターミナルは、時間だけでなく、ティックのヒストリーを備えているので、今ではストックインジケータを開発することが可能です。 "舞台裏で何が起こっているかを確認することができます "、すなわち、実際のボリュームの構成: ボリュームと実行されるトレードの頻度だけでなく、売り手と買い手の相関を確認できます。 つまり、ボリュームをコンポーネントに拡張できるようになりました。 これらのデータは、トレード予測の正確性を大幅に向上させることができます。 同時に、通常のものと比較して、このようなインジケータを開発することは困難です。 この記事では、ストックインジケータの開発の順序と機微、タスクとテストの特徴を徹底的に説明します。 一例として、実際のボリュームを形成する売買ボリュームのデルタ (差分) インジケータを開発します。 インジケータの開発と同時に、ティックフローを操作するルールも同様に記述します。

ただし、実際のボリュームは、一元化された (exchange) 相場でのみ利用可能であることに注意してください。 店頭相場であるため、外国為替には利用されていないことを意味します。 モスクワ交換のデリバティブ相場 (FORTS) の例を通じて、実際のボリュームを検討します。 FORTSに不慣れな場合は、為替価格についての記事を読むことをお勧めします。

対象読者

ティックのデータに関する質問は、最近MQL5.com フォーラムで一般的になっています。 この機能は比較的新しく、常に改善されています。 まず第一に、この記事は、すでにインジケータを書いたことがあり、MetaTrader5 アプリケーションの開発のスキルを向上させたいプログラマ向けです。 この記事はまた株式相場を習得したいと思い、デルタおよび/または類似したティックのインジケータを含む分析に興味があるトレーダー向けでもあります。

1. 準備 サーバーの選択

逆説的ですが、インジケータの開発の出だしは、トレードサーバーの選択である必要があります。 ストックインジケータの正確な操作の必要条件: ブローカーのサーバーを更新する必要があります。 残念ながら、ブローカーのサーバージョンはブロードキャストされず、データが正確かどうかをすぐに把握できるとは限りません。

うまくいけば、マーケットデプスは、この問題を解決できる可能性があります。 それを開くには、左上の画面コーナーにあるツール名の近くにあるテーブルアイコンをクリックし (表示されていない場合は、[表示] タブの [トレーディングボタンを表示] (F8) をクイックし、オプションがオンになっているかどうかを確認します)、または Alt + Bを押します。 [depth of market] ウィンドウで、[Show Time and Sales] ボタンをクリックします。 また、テーブルを右クリックしても、最小ボリュームフィルタが設定されていないことを確認してください。

サーバーが更新されていない場合は、いわゆる "不確実な方向" のトレードをマーケットデプスにブロードキャストします。 詳しく調べてみましょう。 各トランザクションには、イニシエータ (買い手または売り手) があります。 これは、トランザクションのプロパティ (売買) が明確に示されている必要があることを意味します。 トレードの方向が決定されていない場合 (マーケットデプスで N/A としてマーク)、インジケータによって計算された構造の精度 (売買数量の差) デルタに影響を与えます。 更新されていないマーケットデプスは以下のとおりです (図1):

  


図1.更新されたマーケットデプス (左) と古い (右) マーケットデプス

ルール1. サーバーが更新されているかどうかを確認します。

また、低ping サーバーを選択することをお勧めします。 pingが低ければ低いほど、 ターミナルは高速にブローカーのサーバーとデータを交換することができます。 図1をよく見ると、MetaTrader5 boradcasts はミリ秒の精度を扱っているので、ping が少ないほど、データを素早く取得して処理することができます。 現在のサーバーのping をチェックは (必要に応じてサーバーを変更します)、ターミナルの右下隅にあります。


図2. 選択したサーバーの遅延時間は30.58 ミリ秒です

また、このビルドでは、ティックデータに関する現在知られているすべてのエラーが修正されているため、クライアントターミナルをビルド1881以上に更新する必要があることに注意してください。

2. ティックヒストリーを取得するメソッド. MqlTick 形式

正しいティックヒストリーを提供するサーバーを選択したと仮定します。 どうやってそのヒストリーを手に入れましょう? MQL5 言語には、次の2つの関数があります。

今回は両方のインジケータ関数を必要とします。 MqlTick形式でティックを取得できるようになります。 この構造体には、時間、価格、ボリューム、および新しいティックと正確に変更されたデータが格納されています。 3種類のティックヒストリーが得られます。 この型は、フラグによって定義されます。

目的のトレードのティック (COPY_TICKS_TRADE) の流れを必要とします。 CopyTicks 関数の説明のティックタイプについて詳しくは、こちらをご覧ください。

MqlTick 構造体を使用すると、次のフィールドの値を分析できます。

このインジケータは、次のことを行います: ロウソク足ごとにすべてのトレードのティックを取得し、買いボリューム、売りボリューム、ヒストグラムとしてのその差 (デルタ) を表示します。 ロウソクにより多くの買い手があれば、ヒストグラム足は青い。 それ以外は赤です。 すべて簡単です!

3. 初回起動。 ヒストリーの計算

CTicks _ticks (Ticks_article.mqh ファイル) は、インジケータのティックを操作するためのメインオブジェクトとして使用します ティックですべての操作を実行するために使用します。

このインジケータ操作は、2つの主要なブロックに分割されます: ヒストリー計算とリアルタイム計算

//+------------------------------------------------------------------+
//|カスタムインジケータの繰り返し関数                                    |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---最初の起動を確認する
   if(prev_calculated>0)                    //最初の起動ではない場合
     {
      //ブロック2
     }
   else                                     //最初の起動なら
     {
      //ブロック1
     }
//---
   return( rates_total );
  }

ターミナルの初回起動時、またはターミナル (ブロック 1) の更新ボタンをクリックしたときに、ヒストリーのインジケータを計算する必要があります。 当初、ヒストリーとリアルタイム計算の両方に使用する普遍的な計算関数を作ることを計画しました。 しかし、最終的には、変更し、計算速度を加速することにしました。 最初に、ヒストリーは完成した足 (CalculateHistoryBars()) を使用して計算されます。 その後、現在の足 (CalculateCurrentBar()) が計算されます。 操作はすべて以下で説明します。

//--- 1. 初期値によってインジケータバッファを初期化します。
BuffersInitialize(EMPTY_VALUE);
//--- 2. 繰り返し制御パラメータの値をリセットする
_repeatedControl=false;
_controlNum=WRONG_VALUE;
//--- 3. タイマ刻みが保存されている足のリセット時間 ([更新] ボタンをクリック)
_ticks.SetTime(0);
//--- 4. 形成された足のティックのダウンロードを開始するモーメントを設定します。
_ticks.SetFrom(inpHistoryDate);
//--- 5. ダウンロードが開始された時点を確認する 
if(_ticks.GetFrom()<=0)                 //モーメントが設定されていない場合
   return(0);                           //終了
//--- 6. 形成された足のヒストリーのダウンロードを終了するモーメントを設定する
_ticks.SetTo( long( time[ rates_total-1 ]*MS_KOEF - 1 ) );
//--- 7. ダウンロード足のヒストリーを形成
if(!_ticks.GetTicksRange())             //失敗した場合
   return(0);                           //エラーで終了
//--- 8. 形成された足のヒストリーを計算する
CalculateHistoryBars( rates_total, time, volume );
//--- 9. タイマ刻みが保存される足のリセット時間
_ticks.SetTime(0);
//--- 10. 最後の足のティックがダウンロードを開始するモーメントを設定します。
_ticks.SetFrom( long( time[ rates_total-1 ]*MS_KOEF ) );
//--- 11. 最後の足のティックがダウンロード終了したモーメントを設定します。
_ticks.SetTo( long( TimeCurrent()*MS_KOEF ) );
//--- 12. 現在の足のヒストリーをダウンロードする
if(!_ticks.GetTicksRange())             //失敗した場合
   return(0);                           //エラーで終了
//--- 13. コピー終了時のリセット
_ticks.SetTo( ULONG_MAX );
//--- 14. 得られたヒストリーの最後のティックの時間を記録
_ticks.SetFrom();
//--- 15. 現在の足の計算
CalculateCurrentBar( true, rates_total, time, volume );
//--- 16. リアルタイムでトレーリングのコピーのタイマ刻み数を設定する
_ticks.SetCount(4000);

インジケータコードは、広範なコメントを持っているので、メインポイントのみに焦点を当てます。

ポイント3. "足のリセット時間は、ティック" に保存されます。 ティックで動作するオブジェクトには、ロウソク足の開始時刻が含まれ、対応するティックが保存されます。 [更新] をクリックすると、最初からターミナルでインジケータが再計算されます。 ティックが適切に必要なロウソクに保存されるためには、その時間はリセットする必要があります。

ポイント4. "形成された足のティックのダウンロードを開始するモーメントを設定します "。 ティックヒストリーの取得は、非常に時間のかかる操作になります。 したがって、ユーザーにダウンロードの開始日を指定する機会を与える必要があります。 inpHistoryDate パラメータはそのものです。 値が0の場合、ヒストリーは現在の日付の先頭からダウンロードされます。 SetFrom (datetime) メソッドのこのプロトタイプでは、時間は秒単位で渡されます。 既に述べたように、インジケータの形成された足の計算が最初に行われます。

ポイント5 "ダウンロードの開始モーメントが正しいかどうかを確認してください "。 ポイント4で受信した値を確認します。

ポイント6. "形成された足のヒストリーのダウンロードを完了するモーメントを設定します "。 確定足のヒストリーのダウンロードを完了する時は現在のロウソク (rates_total-1) を開ける前のミリ秒です。 この場合、ダウンロードを完了したモーメントは ' long ' 型です。 メソッドにパラメータを渡すときに、クラスが ' datetime ' 型で渡されるメソッドも機能する場合に、' long ' 型が渡されていることを明示的に示す必要があります。 SetTo() メソッドの場合、クラスは ' datetime ' 型引数でオーバーロードしません。 ' long ' 型パラメータを明示的に安全な側にすることをお勧めします。

ポイント7. "形成された足によるダウンロードのヒストリー "。 ヒストリーは、GetTicksRange() 関数を使用して取得され、CopyTicksRange() 関数のラッパーであり、エラーのチェックが追加されます。 ダウンロード中にエラーが発生した場合は、次のティックでヒストリー全体が繰り返しリクエストされます。 以下に添付された Ticks_article.mqh ファイルには、この関数に関する詳細、およびティックを操作するためのその他の関数があります。 

ポイント8. "形成された足のヒストリーの計算 " 確定足の計算については、該当する記事のセクションで詳しく説明します。

ポイント9-12. 足による計算が完了しました。 さて、現在のロウソクを計算しましょう。 コピー範囲が設定され、現在のロウソクのティックがここで取得されます。

ポイント13 "コピーが終了したモーメントをリセットします "。 さらに、_ticks オブジェクトを使用してティックを取得し続けますが、ヒストリーをダウンロードするときに、利用可能なヒストリー全体の最後のティックが到着してから、1つの時点から別のものにするのではありません。 したがって、コピーが終了するモーメントをリセットすることをお勧めします-リアルタイムで計算するときにそれ以上を必要としません。

ポイント14 "取得したヒストリーの最後のティックの時刻を記録 "。 リアルタイムでデータのコピーを開始するモーメントとして、後で得られたヒストリーの最後のティックの時間が必要になります。

ポイント15 "現在の足の計算 "。 現在の足の計算はまた、記事の別の部分で説明され、形成された足の計算メソッドとは重要な差があります。

ポイント16 "リアルタイムでトレーリングのコピーのティック数を設定 "。 以前は、GetTicksRange() メソッドでラップされた CopyTicksRange() 関数を使用してティックを取得しました。 ただし、リアルタイムでは、GetTicks() メソッドにラップされた CopyTicks() 関数を使用します。 SetCount() メソッドは、トレーリングのリクエストに対してタイマ刻みの数を設定します。 ターミナルは、高速アクセスの各シンボルの4096ティックを格納するため、4000を選択します。 ティックのリクエストは最高速度で実行されます。 値を設定すると、速度 (~ 1 ミリ秒) を取得するティックには影響しませんが、100または4000になります。

計算関数について詳しく見てみましょう。

4. 確定足によるヒストリー計算関数

関数自体は次のようになります。

//+------------------------------------------------------------------+
//|確定ヒストリー足の計算関数                                           |
//+------------------------------------------------------------------+
bool CalculateHistoryBars(const int rates_total,    //計算される足の数
                          const datetime& time[],   //足の開始時間の配列
                          const long& volume[]      //実数のボリューム値の配列
                          )
  {
//---総ボリューム
   long sumVolBuy=0;
   long sumVolSell=0;
//---バッファに書き込むための足インデックス
   int bNum=WRONG_VALUE;
//---配列のティック数を取得します。
   const int limit=_ticks.GetSize();
//---すべてのティックによるループ
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //---ロウソクのティックが書かれている定義
      if(_ticks.IsNewCandle(i))                         //次のロウソク足が形成を開始する場合
        {
         //---形成された (完全な) ロウソクのインデックスが保存されるかどうか確認しなさい
         if(bNum>=0) //保存した場合
           {
            //---ボリューム値が保存されているかどうかを確認する
            if(sumVolBuy>0 || sumVolSell>0) //すべてのパラメータが保存されている場合
              {
               //---総ロウソクのボリュームを監視
               VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
            //---バッファに値を追加する
            DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
           }
         //---以前のロウソク足のボリュームをリセットする
         sumVolBuy=0;
         sumVolSell=0;
         //---開始時間に従ってロウソクのインデックスを置く
         bNum=_ticks.GetNumByTime(false);
         //---インデックスが正しいかどうかを確認する
         if(bNum>=rates_total || bNum<0) //インデックスが正しくない場合
           {
            //---ヒストリーを計算せずに終了
            return( false );
           }
        }
      //---必要なコンポーネントのティックにボリュームを追加する
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
     }
//---最後に形成されたロウソクのボリューム値が保存されているかどうかを確認
   if(sumVolBuy>0 || sumVolSell>0) //すべてのパラメータが保存されている場合
     {
      //---合計ロウソク足ボリュームを追跡する
      VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
     }
//---バッファに値をエントリーします。
   DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
//---計算の完了
   return( true );
  }

この関数の背後にあるアイデアは、現在の期間の形成ロウソク足によって得られたティックをソートすることです。各ロウソク足の買いと売りのボリュームの差を取得し、インジケータバッファに取得したボリュームとデルタの値を入力してください.

前に述べたように、最初にヒストリーおよび実時間計算の1つの共通関数を作ることを計画していました。 しかし、形成された足のヒストリーを計算するための関数を追加することにより、目標を追求します。:

計算アルゴリズムの完全な説明とティックヒストリーの操作を以下に示します。

5. 現在のロウソク足を計算する関数

インジケータコードの CalculateCurrentBar() 関数に細心の注意を払ってください。

//+------------------------------------------------------------------+
//|現在のロウソク足計算関数                                             |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   //関数の最初の起動のフラグ
                         const int rates_total,    //計算されるフラグの数
                         const datetime& time[],   //足のオープンの時間の配列
                         const long& volume[]      //実数のボリューム値の配列
                         )
  {
//---総ボリューム
   static long sumVolBuy=0;
   static long sumVolSell=0;
//---バッファに書き込むための足インデックス
   static int bNum=WRONG_VALUE;
//---最初の起動フラグを確認する
   if(firstLaunch)                                 //最初の起動の場合
     {
      //---静的パラメータのリセット
      sumVolBuy=0;
      sumVolSell=0;
      bNum=WRONG_VALUE;
     }
//---配列の最後から2番目のティックのインデックスを取得します。
   const int limit=_ticks.GetSize()-1;
//---'limit ' ティック時間
   const ulong limitTime=_ticks.GetFrom();
//---すべてのティックのループ (最後のタイマを除く)
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //--- 1. i 番目のティック時間をリミットティック1と比較します (ループ完了を確認してください)。
      if( _ticks.GetTickTimeMs( i ) == limitTime ) //ティック時間がリミットティック1と等しい場合
         return;                                   //終了
      //--- 2. チャート上に存在しないロウソク足が形成を開始するかどうかを確認する
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                //ロウソクが形成を開始した場合
        {
         //---ログが保持されているかどうかを確認する
         if(inpLog)
            Print(__FUNCTION__,": ATTENTION! Future tick ["+GetMsToStringTime(_ticks.GetTickTimeMs(i))+"]. Tick time "+TimeToString(_ticks.GetTickTime(i))+
                  ", time[ rates_total-1 ]+PerSec() = "+TimeToString(time[rates_total-1]+PeriodSeconds()));
         //--- 2.1. 次のティックリクエストの時刻を設定します
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //---終了
         return;
        }
      //--- 3. ティックが保存されるロウソク足を定義する。
      if(_ticks.IsNewCandle(i))                    //次のキャンドルが形成を開始する場合
        {
         //---3.1 形成された (完全な) ロウソクのインデックスが保存されるかどうか確認
         if(bNum>=0)                               //インデックスが保存されている場合
           {
            //---ボリューム値が保存されているかどうかを確認する
            if(sumVolBuy>0 || sumVolSell>0)        //すべてのパラメータが保存されている場合
              {
               //---3.1.1. ロウソクの総ボリュームを管理する
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //---3.2 以前のロウソク足ボリュームをリセットする
         sumVolBuy=0;
         sumVolSell=0;
         //---3.3 現在のロウソク足インデックスを記録
         bNum=rates_total-1;
        }
      //--- 4. ティックのボリュームを必要なコンポーネントに追加する
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
      //--- 5. バッファに値をエントリーします。
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
     }
  }

前の関数 CalculateHistoryBars() に似ていますが、独自の関数があります。 詳しく調べてみましょう。 関数プロトタイプは以下のとおりです。

//+------------------------------------------------------------------+
//|現在のロウソク足計算関数                                             |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   //関数の最初の起動フラグ
                         const int rates_total,    //計算される足の数
                         const datetime& time[],   //足のオープンの時間の配列
                         const long& volume[]      //実数のボリューム値の配列
                         )

CalculateCurrentBar() は2つのケースで使用することに注意してください: 最初の起動時に現在のロウソク足のヒストリーを計算し、リアルタイムで計算を実行するとき。 firstLaunch フラグを使用すると、計算モードを選択できます。 モード間の唯一の差は、買いと売りの合計を含む静的変数だけでなく、合計を含むバッファ内のロウソクのインデックスとその差 (デルタ) が、最初の起動時にゼロにリセットされているということです。 もう一度、実際のボリュームだけがインジケータで使用されていることを強調したいと思います!

//---総ボリューム
   static long sumVolBuy=0;
   static long sumVolSell=0;
//---バッファに書き込むための足インデックス
   static int bNum=WRONG_VALUE;
//---最初の起動フラグを確認する
   if(firstLaunch)                                 //最初の起動の場合
     {
      //---ボリュームの合計をリセットする
      sumVolBuy=0;
      sumVolSell=0;
      //---ロウソク足インデックスをリセットする
      bNum=WRONG_VALUE;
     }

静的変数を宣言すると、配列の最後のティックインデックスと時刻が取得されます。

//---配列を取得する最後のティックインデックス
   const int limit=_ticks.GetSize()-1;
//---'limit ' ティック時間
   const ulong limitTime=_ticks.GetFrom();

インデックスは、タイマ刻みの繰り返しループの区切りシンボルとして機能します。 最後のティックの1つに一致する時間を持つ最後のティックとティックが計算に含まれないという条件を確立してみましょう。 なぜでしょうか? トレードのティックは、単一の相場オーダーが異なる相手方からの指値オーダーに実装されている場合にバンドルに到着することがあります。 ティック (取引) のバンドルは、(ミリ秒の精度で) 同時に実行され、同じタイプ (買いまたは売り) を持つトレードで構成されています (図3)。 複数のティックバンドルは、同一ミリ秒以内に到着したものとしてターミナルに表示できることに注意してください。 自身で見るためには、次のtest_getTicksRange のスクリプトを起動してください。

図3. ティックバンドル (成行買いオーダー 26ロットから成る4取引)

ボリュームを正しく考慮するために、1つのティック・バンドルは、ターミナルに完全に渡されたとき、すなわちサブシークエントの時間 (図4) で約定した取引が利用可能になったときに一度だけ計算する必要があります。


図4. .373でトレードを行い、.334でバンドルを計算。

バンドルがパーツに到着する可能性があるため、バンドルに続く取引が利用可能になるまで、バンドルがターミナルに完全に到着したことを確認することはできません。 ここでは詳細に触れないので、まずは一度このままいきます。 したがって、ルール2を定義することができます:

ルール2. ティックバンドルは、そのバンドルに続くティックを受け取った後にのみ計算する必要があります。

p で最後に得られたティックの時間を保存します。 最初の起動アルゴリズムの13. さて、変数に limitTime を書いて使ってみましょう。

次に、ティック計算ループに直接移動してみましょう。

//--- 1. i 番目のティック時間をリミットティック1と比較します (ループ完了を確認してください)。
      if( _ticks.GetTickTimeMs( i ) == limitTime ) //ティック時間がリミットティック1と等しい場合
         return;                                   //終了

ポイント1. "ティック時間と最後のティックの1つを比較する "。 前述したように、最後のティックは、計算が形成されたティックバンドルによってのみ実行されるため、考慮されません。 しかし、また、最後のティックバンドルが部分的にコピーされます。 つまり、計算からバンドルのすべてのティック (存在する場合) を除外する必要があります。

//--- 2. チャート上に存在しないロウソク足が形成を開始するかどうかを確認する
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())

ポイント2. "チャートに存在しないロウソクが形成を開始するかどうかを確認してください "。 これは、少し奇妙に聞こえるかもしれません。 どのようにしてロウソクがチャートに存在しないことがあるのでしょうか? この質問に答えるためには、ターミナルのティックデータの処理/受信の特殊性を理解する必要があります。 この特殊性は、サービスデスクを介して開発者との広範なコミュニケーションの結果として明らかになりました。 ここで説明します: 

ターミナルティックは、インジケータと EA のパフォーマンスに関係なく、別のフローで収集されます。 ロウソク足は別のフローで作られます-インジケータの実行1. フローは互いに同期されません。 ロウソクにティックが適用された後、インジケータが計算されます。 1ティックも抜けていません。 つまり、CopyTicks() 関数を呼び出すことにより、足に適用されたデータと比較して、より新しいティックデータを取得できます。

実際の練習では、次のことを意味します。 rates_total-1 ロウソク足を計算するとき、インジケータはまだ不完全である次のロウソクのティックを得ることができます (ティックはまだ適用されていない)。 そのような状況を回避するには (配列の範囲外のエラーと同様に)、このチェックを追加する必要があります。

ルール3 ローソク足チャートにまだ現れていないティックが得られることに注意してください。

"未来の" ティック (適切な形成ロウソク足なし) が検出された場合には、次のティックのリクエストが発生する (ポイント 2.1) 時間を書く必要があります。 加えて、すぐに、新しいティックとチャート上の新しいロウソクの形成を待っている間、ループと関数を終了する必要があります:

//--- 2. チャート上に存在しないロウソク足が形成を開始するかどうかを確認する
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                //ロウソクが形成を開始した場合
        {
         //--- 2.1. 次のティックリクエストの時刻を設定します
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //---終了
         return;
        }

次のアルゴリズムは、ほぼ完全に CalculateHistoryBars() 関数と一致します。 もう少し詳しく考えてみましょう。

//--- 3. ティックが保存されるロウソク足を定義する。
      if(_ticks.IsNewCandle(i))

ポイント3. ティックが保存されるロウソク足を定義します。 ここでは、ティックの i 番目の時間とロウソクのオープンタイムが比較保存されます。 i番目ティックの時間がロウソクボーダーを越えて行けば、ロウソクの開始の時間は変えられ、次のロウソクを準備するためのアルゴリズムは誘発されます:

//--- 3. ティックが保存されるロウソク足を定義する。
      if(_ticks.IsNewCandle(i))                    //次のロウソク足が形成を開始する場合
        {
         //---3.1 形成された (完全な) ロウソクのインデックスが保存されるかどうか確認
         if(bNum>=0)                               //インデックスが保存されている場合
           {
            //---ボリューム値が保存されているかどうかを確認する
            if(sumVolBuy>0 || sumVolSell>0)        //すべてのパラメータが保存されている場合
              {
               //---3.1.1. ロウソクの総ボリュームを管理する
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //---3.2 以前のロウソク足ボリュームをリセットする
         sumVolBuy=0;
         sumVolSell=0;
         //---3.3 現在のロウソク足インデックスを記録
         bNum=rates_total-1;
        }

ポイント3.1. 形成されたロウソクのインデックスが保存されるかどうか確認。 ヒストリー計算モード (最初の起動) では、このチェックは、不適切なインデックス (-1) の下の時間とボリュームの配列へのアクセスを妨げます。 次に、ロウソク足で取引が行われたかどうかを確認します。 トレードがなかったので、ボリュームコントロールは必要ありません。

ポイント 3.1.1. ボリュームコントロールの合計。 VolumeControl() の手順では、ロウソクあたりのインジケータによって蓄積された取引ボリュームを合計し、 "参照 " ボリューム、すなわち、取引所からの実際のボリューム(Volume[]配列によって得られる値)と比較されます。 取引所ボリュームが累積されたものと一致する場合は、さらに次の計算に進みます。 しかし、一致していない場合はどうなりますか? そんなことがあるか疑問に思うかもしれません。 総ボリュームは同じです。 唯一の差は、片方はインジケータで計算したもので、片方は取引所から得たものです。 ボリュームは間違いなく一致する必要があります! 

まあ、正しいです。 そうなるはずです。 このルールはすべてのロウソク足に適用されるべきです。 我々のインジケータがすることは正確には何でしょうか。

これまでのところかなり簡単です。 しかし、リアルタイムで (rates_total-1 ロウソク)、前述の "未来 " ティック (ルール 3) については、新しいロウソクのティックが到着したときに、チャートロウソク足はまだ形成されていませんが表示について記録する必要があります。 この特徴は、ボリュームコントロールにも影響します! ティックを処理する場合、インジケータには、volume[] 配列内の古いボリューム値がまだ残っています (値は変更されません)。 これは、インジケータによって収集されたボリュームを、volume[] 配列1に正しく比較することができないことを意味します。 実際には、volume[rates_total-1] 参照ボリュームがインジケータによって収集された (sumVolBuy + sumVolSell) ボリュームの合計と一致しないことがあります。 VolumeControl() プロシージャには、次の2つのソリューションがあります。

  1. ロウソクのボリュームを再計算し、CopyRealVolume() 関数を介して得られた基準値と比較します。
  2. 最初のオプションで問題が解決しない場合は、新しいロウソク足が形成されたときにボリュームコントロールフラグが設定されます。

従って、最初のメソッドは新しいロウソクの形成の前に制御問題を解決することを試み、第2のメソッドは形成の後で問題を解決するために保証されます。

ポイント3.2. "以前のロウソク足ボリュームをリセット "。 新しいロウソク足が形成されたら、ボリュームカウンタをゼロにリセットします。

ポイント3.3. "現在のロウソク足インデックスを記録"。 計算関数を形成された足と現在のロウソクで計算するための関数に分離のもう一つの利点。 現在のロウソク足インデックスは、常に rates_total-1 に等しくなります。

//--- 4. ティックのボリュームを必要なコンポーネントに追加する
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);

ポイント4. 合計ボリュームにティックボリュームを追加します。 まず、分析したティックのフラグを使用して、変更されたデータを確認します。

//+------------------------------------------------------------------+
//|ティック・ボリュームを合計ボリュームに追加する                          |
//+------------------------------------------------------------------+
void AddVolToSum(const MqlTick &tick,        //チェックされたティックパラメータ
                 long& sumVolBuy,            //合計買いボリューム (out)
                 long& sumVolSell            //合計売りボリューム (out)
                )
  {
//---ティックの方向を確認する
   if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY && ( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) //ティックが両方向の場合
        Print(__FUNCTION__,": ERROR! Tick '"+GetMsToStringTime(tick.time_msc)+"' is of unknown direction!");
   else if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY)   //買いのティックの場合
        sumVolBuy+=(long)tick.volume;
   else if(( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) //売りティックの場合
        sumVolSell+=(long)tick.volume;
   else                                                  //トレードティックでなければ
        Print(__FUNCTION__,": ERROR! Tick '"+GetMsToStringTime(tick.time_msc)+"' is not a trading one!");
  }

ここで再びルール 1に焦点を当てたいと思います。 不明な方向のトランザクションをブロードキャストするサーバー上でタスクが行われた場合、そのトランザクションを開始した人が売り手なのか買い手なのかを決定することはできません。 したがって、ジャーナルは対応するエラーを続行します。 イニシエータが決定された場合、ボリュームは買いまたは売りの合計ボリュームに追加されます。 フラグにトランザクションのイニシエータに関するデータが含まれていない場合は、エラーも受信されます。

//--- 5. バッファに値をエントリーします。
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);

ポイント5 バッファに値を追加します。 DisplayValues() プロシージャは、インジケータバッファのインデックスを追跡し (実現するために呼び出し文字列インデックスを関数に渡します)、デルタ計算とデルタの作成、およびボリュームの買いと売りは、バッファに対して行います。

6. リアルタイムで計算

計算ブロックアルゴリズムをリアルタイムで説明しましょう:

//--- 1. 新しい足の形成を確認してください
if(rates_total>prev_calculated) //新しい足の場合
  {
   //---空の値によって rates_total-1 バッファインデックスを初期化する
   BuffersIndexInitialize(rates_total-1,EMPTY_VALUE);
   //--- 2. rates_total 足のボリュームを追跡するかどうかを確認します。
   if(_repeatedControl && _controlNum==rates_total-2)
     {
      //--- 3. Re-check
      RepeatedControl(false,_controlNum,time[_controlNum]);
     }
   //--- 4. 再チェック値をリセットする
   _repeatedControl=false;
   _controlNum=WRONG_VALUE;
  }
//--- 5. 新しいティックのダウンロード
if(!_ticks.GetTicks() )               //失敗した場合
   return( prev_calculated );         //エラーで終了
//--- 6. 得られたヒストリーの最後のティックの時間を記録
_ticks.SetFrom();
//--- 7. リアルタイム計算
CalculateCurrentBar(false,rates_total,time,volume);

ポイント1. 新しい足の形成を確認してください。 このチェックは非常に重要です。 2.1.1 でわかったように、メインの計算関数のボリューム制御手順 (リアルタイム計算) でチェックが渡されない場合は、新しい足の形成時に渡す必要があります。 これぞまさにライトモーメント(ふさわしい)です!

ポイント2. rates_total 足のボリュームを追跡するかどうかを確認します。 繰り返し制御フラグがチェックされ、新しく形成された rates_total ロウソク足に対して行われる場合は、再チェックを行います (p. 3).

ポイント3. 再チェックを実行します。 既に述べたように、再確認の間に、ロウソクごとのすべてのティックは受け取られます。 その上、買いボリューム・売りボリュームを定義し、デルタを計算し、参照値のボリュームの合計を比較します。

ポイント5 新しいティックをダウンロードします。 前のインジケータの起動中に最後のティックが到着してからティックを取得します。 リアルタイムで計算する場合は、CopyTicks() 関数を適用する GetTicks() 関数を使用してティックを取得します。

ポイント6. 最後のティック時間を記録。 p で最後に取得したティックの時刻です。 5またはヒストリーの計算後。 ティックのヒストリーは、次のインジケータの起動中にその時点からリクエストされます。

ポイント7. リアルタイムで計算します。 前述したように、CalculateCurrentBar() プロシージャは、ヒストリーとリアルタイム計算の両方で使用します。 firstLaunch フラグには、その役割があります。 この場合、' false ' に設定されます。

7. ストラテジーテスターで稼働させることの特徴

ストラテジーテスターを使用する場合、独自の関数を持つ別のプログラムであることを常に念頭においておく必要があります。 テスターがターミナルと同じことをしても、ターミナルと同じようにやってくれるというわけではありません。 同様の状況 (テスター開発のこの段階で) は、ティックデータを使用するプログラムで発生します。 インジケータが正しく計算されているにもかかわらず (ボリュームコントロールは正常に実行されます)、テスターのインジケータは多少異なるように計算されます。 この理由はティックバンドルの処理にあります。

1つのティック内で複数のトランザクションが発生する (つまり、ティック・バンドルを取得できる) ターミナルとは異なり、テスターでは、1つのバンドルの複数のティックが次々に到着した場合でも、各ティックは個別に受信されます。 アプリケーションから test_tickPack テストインジケータを起動することで、このことを確認できます。 おおよその結果は次のとおりです。

2018.07.13 10:00:00   OnCalculate: Received ticks 4. [0] = 2018.07.13 10:00:00.564, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 2. [0] = 2018.07.13 10:00:00.571, [1] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 3. [0] = 2018.07.13 10:00:00.571, [2] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 4. [0] = 2018.07.13 10:00:00.571, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 5. [0] = 2018.07.13 10:00:00.571, [4] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 6. [0] = 2018.07.13 10:00:00.571, [5] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 7. [0] = 2018.07.13 10:00:00.571, [6] = 2018.07.13 10:00:00.572 FLAG_BUY

それぞれ試すことができます。 F12 キーを連続して押すことによって、 "すべてのティックを実際のティック " モードに基づいて設定するようにしてください。 ティックは、厳密に1つずつ追加されます! しかし現実には、このバンドルは、パーツ、または1つの部分でターミナルに入ることができるが、おそらく "ワンアットアタイム" モードではありません。 これは良くも悪くもありません。 このことを念頭においておいてください。

結論

この記事では、ティックインジケータを開発する上で、微妙な側面と、私も含めほとんど多くの人が直面するであろう問題点について紹介しました。 私の経験が、ティックデータに関するより多くのアプリケーションの道しるべとなり、MetaTrader5 プラットフォームのさらなる発展に弾みを持たれせればと思います。 トレードティックに関して何か他の特殊性を知っている場合、または不正確さを発見した場合、気兼ねなく連絡してください。 喜んでこのトピックのディスカッションをしましょう。

最終的な結果を以下に示します。 青い足は、特定のロウソクにおける買い手の優位性を示しています。一方、赤は売り手優位です。


図5. RTS-6.18 のデルタインジケータ

実際のボリュームの推定は、価格変動の理解を促し、株式相場の分析において新天地を開くでしょう。 このインジケータは、ティックデータ分析に基づいて開発できるごく一例にすぎません。 実際のボリュームに基づいて株価インジケータを作成することは非常に意味のあるタスクです。 この記事では、このようなインジケータを作成し、トレードを改善するのに役立つことを願っています。

インジケータ自体に興味を持っている場合は、改善されたバージョンがすぐにプロフィールのプロダクトセクションで公開されると思います。 トレード頑張ってください!

この記事で使用したファイル

ファイル名 種類 詳細
 1. Delta_article.mq5  インジケータファイル  デルタインジケータの実装
 2. Ticks_article.mqh  クラスファイル  ティックデータを操作するための補助クラス
 3. test_getTicksRange.mq5  スクリプトファイル  複数のティック・バンドルを1ミリ秒で受信するテスト・スクリプト
 4. test_tickPack.mq5  インジケータファイル  テスターでティックが受信されているかどうかを確認するためのテストインジケータ