English Русский 中文 Español Deutsch Português
preview
手動のチャート作成および取引ツールキット(第III部)最適化と新しいツール

手動のチャート作成および取引ツールキット(第III部)最適化と新しいツール

MetaTrader 5 | 18 1月 2022, 12:17
568 0
Oleh Fedorov
Oleh Fedorov

はじめに

以前の記事(12)では、Shortcutsライブラリについて説明し、エキスパートアドバイザーとしてのライブラリの使用例を示しました。ライブラリはある程度生物に似ています。それは生まれ、公に示され、それが「生きる」環境に出会います。しかし、環境は変化しており、独自の法則を持ちます。主な法則の1つは「改善」です。したがって、常にさらに発展し、改善する必要があります。この記事では、この改善プロセスの結果の一部を示します。

ライブラリは5つのファイルで構成されています。

メインファイルはShortcuts.mqhです。このファイルには、キーストローク処理ロジックが格納されています。さらに、これはエキスパートアドバイザーまたは指標に直接含まれる唯一のファイルです。残りのファイルはインクルードされ、初期化されます。

ファイルGlobalVariables.mqhには、すべてのグローバル変数が含まれています。これらは主に、線の色、線の太さ、線の伸び係数などの構成可能な設定です。

ファイルMouse.mqhには、マウスの動きを処理するクラスの説明が含まれています。現在のカーソル座標をピクセル座標と「価格-時間」座標の両方で保存し、現在のバー番号を保存します。

Utilites.mqhにはヘルパー関数が含まれています。バーの極値、線の交点、および描画に直接関係しないがそのすべての側面を決定するその他の有用なパラメータが計算されます。

ファイルGraphics.mqhは、他のファイルのデータに基づいて描画する役割を果たします。主にこのファイルの関数はShortcuts.mqhファイルを呼び出します。

関数は必ずしも厳密にグループ化されていません。一部の計算は、描画関数内で行うことができます。これまでのところ、この構造を開発して維持するのが便利ですが、おそらくいつか全体的な配置を改善すると思います。

この実装は、ライブラリコードを指標で使用する方法を示しています。


ライブラリパフォーマンスの最適化

ここでの変更は最小限です。

初期ライブラリバージョンで指標ではなくエキスパートアドバイザーを選択したのはなぜでしょうか。簡単です。すべてのエキスパートアドバイザーは、独自の実行スレッドで実行されます。理想的にはそれらは相互に影響を与えないため、複数のチャートでキーボードショートカットを処理する必要がある場合でも、ターミナルの速度はそれほど低下しません。

ただし、エキスパートアドバイザーの目的は取引ですが、このプログラムは取引操作を実行しません。さらに、指標がチャートに添付されている場合、そのチャートで別のエキスパートアドバイザーを実行する方がはるかに簡単です。したがって、指標を実装することにしました。ここでパフォーマンス速度の問題が発生します。これは、ユーザーがウィンドウをたくさん開いている場合に特に重要です。たとえば、ユーザーが40個(それより多い場合もあります)のタブを開いていれば、すべてのチャートが一度にキーストロークを処理する場合、キーストロークチェックを処理することさえ困難になります。

ここで私は悟りました。「何故すべてのチャートを処理する必要があるのでしょうか。」すべてのチェックは、アクティブなウィンドウでのみ実行する必要があるのです。

コードは非常に単純です。

/* Shortcuts.mqh */

void CShortcuts::OnChartEvent(
  const int id,
  const long &lparam,
  const double &dparam,
  const string &sparam
)
 {
 //...

  if(ChartGetInteger(0,CHART_BRING_TO_TOP)==false)
   {
    return;
   }
 
 //...

関数の最初に、このチャートがアクティブである、つまりチャートが前面にあることを確認するチェックを追加する必要があります。falseの場合、何もする必要はありません。

実際のパフォーマンスや速度の向上をパーセンテージで測定することはしませんでしたが、ライブラリをダウンロードして実際に多くのタブを使用したユーザーのレビューによると、アプリケーションの応答性は指標の形でも高くなっています。これはまさに私たちが必要としているものです。

また、アプリケーションの目的も忘れないでください。

まず、アプリケーションは一時的な機能を実行するように設計されているため(つまり、ティックごとに呼び出されるわけではない)、非常に限られた時間だけリソースを消費します。

第二に、コンピュータが十分に強力でない場合、グラフィックス自体が速度の問題の原因になります。チャート上のオブジェクトが多いほど、それらの処理は難しくなります。それにもかかわらず、それはグラフィックライブラリであるため、描画のコストを受け入れ、描画されるものを注意深く制御する必要があります。

第三に、プログラムで最もリソースを消費する関数は極値検索関数です。しかし、それをより速く実装する方法がわかりませんので、現在の実装は今のところ最適だと思います。とにかく、この関数はそれほど頻繁には呼び出されません。線やその他の便利な形状を描画する場合にのみ呼び出されるため、これまでのところ、最適でないパフォーマンスは無視できます。

他のすべての関数はそれほど頻繁に呼び出されず、十分に高速に動作するため、それらについて説明する必要はありません。


コードリファクタリング: 接続の管理

以前の記事で示したコードバージョンは、アプリケーションがモノリシックであり、そのパーツが個別に使用されないことを前提としています。そのため、コードはグローバル設定を直接使用しましたが、一部のユーティリティはマウスクラスに依存していました。

コードをより速く書くことはできましたが、再利用の観点からは不便でした。マウスやグラフィックを使用しない新しいプロジェクトに既存のユーティリティファイルを接続する必要がある場合でも、グローバル設定ファイルとマウスクラスを接続する必要があります。

これは間違っているし不便です。

そのため、コードを少し変更することにしました。すべてのグローバル変数は引き続き使用されます。それらは設定のため、削除できません。

メインクラスにこれらの変数のコピーを含むprivateフィールドを追加しました。これらの値を保存するには、特別な「public」関数を追加する必要があります。値を読み取るためにも必要です。

次のようになります。

private:
  /* Fields */
  //---
  static int          m_TrendLengthCoefficient;

public:
  /* Methods */
  //---
  static int          TrendLengthCoefficient(void) {return m_TrendLengthCoefficient;}
  //---
  static void         TrendLengthCoefficient(int _coefficient) {m_TrendLengthCoefficient=_coefficient;}

既存の設定の数を考えると、このプロセスは長くて退屈なようです。

しかし、メリットは素晴らしいものです。まず、クラスは外部ファイルから独立します。クラスを使用したい場合は、必要な変数のみを使用し、必要に応じて値を設定できます。

第二に、このような変数は実行時に変更できます。たとえば、一点から線へのファンを構築する関数を書くとします。各線は前の線の2倍の長さで、さまざまな角度で広がります。これはどうすればできるでしょうか。CUtilitesクラスの現在の実装を使用して、各描画の前に、例で説明されているTrendLengthCoefficientパラメータを設定し、開始点を同じ座標に配置し、最終点を任意の半径の円に配置する必要があります。

第三に、クラス内のデータは任意の便利な方法でグループ化できます。長方形に関連するデータ、対角線に関連するデータ、レベルに関するデータなどを個別に格納する構造体や完全なクラスを作成することもできます。エンドユーザーの観点からは、インターフェイス(データへのアクセス方法)は変更されていません。

第四に、データは必ずしもRAMに保存する必要はありません。変数をターミナルのグローバル変数に保存したり、通常はファイルやデータベースに保存したりできる場合があります。一部のパラメータは、他のパラメータに基づいてオンザフライで計算できます。上記の例に示すように、この「正しい」データアクセスの編成により、エンドユーザーはデータ構造体の直接実装について心配することなくコードを再利用できます。過剰なコードを記述したり、不要な関数を呼び出したり、インスタンスが作成されるたびに必要な変数を初期化する必要があるにもかかわらず、とにかく非常に便利です。

したがって、現在のライブラリバージョンで可能なことはすべて新しいスタイルに一致するように書き直し、ユーティリティファイルを任意のプロジェクトで「そのまま」使用できるようにしました。

マウスクラスには元々関連するすべての関数が含まれていたため、修正するものはありませんでした。描画クラスは、ユーティリティなしでは役に立ちませんが、新しい形式のフィールド用に、その中のすべての外部設定を変更しました。

これが結果です。マウスとユーティリティは完全に独立したクラスであり、単独または組み合わせで使用できます。描画クラスは両方を使用しますが、クラスを初期化する必要があるディスパッチャを除いて、他の外部ファイルから独立しています。キーボードショートカットを含むクラスは、管理クラス、つまり、コード全体を必要に応じて機能させるディスパッチャです。クラスの相互接続がはるかに弱くなり、上記のメリットが得られます。


「クロスヘアー」ツール

以前のバージョンのライブラリでは、トレンドラインを描画するときに、時間と価格のレベルを調整するためにラインの最後にクロスヘアーが描画されていました。それを作成するために、垂直と水平の2本の単純な線を使用しました。ただし、チャートの任意の点に表示するには、HとIの2つのキーを押す必要がありました。これは便利な場合もあれば、もっと簡単にしたい場合もあります。クロスヘアーを追加したのはこのためです。

ツールは一般的な方法で機能します。カーソルを目的の場所に移動し、Xを押します。これによりクロスヘアーが生成されます。以下は関数のコードです。


/* Graphics.mqh */

//+------------------------------------------------------------------+
//| Draws a crosshair at specified coordinates. If the coordinates   |
//|   are not set, the mouse pointer coordinates are used.           |
//+------------------------------------------------------------------+
//| Parameters:                                                      |
//|   datetime _time - crosshair time                                |
//|   double _price - price level                                    |
//+------------------------------------------------------------------+
void CGraphics::DrawCross(datetime _time=-1,double _price=-1)
 {
  datetime time;
  double price;
//---
  if(_time==-1)
   {
    time=CMouse::Time();
   }
  else
   {
    time=_time;
   }

  if(_price==-1)
   {
    price=CMouse::Price();
   }
  else
   {
    price=NormalizeDouble(_price,Digits());
   }
  DrawSimple(OBJ_HLINE,time,price);
  DrawSimple(OBJ_VLINE,time,price);
  
 }

以前のバージョンのコードに精通している人にとって、ここでは特別なことは何もありません。まず、座標を設定します。これらの座標がパラメータを使用して渡される場合、正確にこれらの値が使用されます。デフォルトのパラメータが設定されている場合は、マウスポインタの座標が使用されます。

さらに、2番目の記事で説明した関数を使用して線を描画します。

この関数が正確に記述された方法で機能するためには、特定のイベント(Xキーを押す)時にShortcuts.mqhファイルから呼び出す必要があります。

/* GlobalVariables.mqh */
  
  // ...
  
  input string   Cross_Key="X";                       // Crosshair where the mouse was clicked
  
  // ...
  /* Shortcuts.mqh */
  
  void CShortcuts::OnChartEvent( /* ... */ )
    switch(id)
     {
       case CHARTEVENT_KEYDOWN:
       
       // ... 
       
       //--- Draw a crosshair
       if(CUtilites::GetCurrentOperationChar(Cross_Key) == lparam)
        {
         m_graphics.DrawCross();
        }
     }

任意の極値ツールによるトレンドライン

左側と右側に特定の数のバーがある極端なトレンドラインを作成する機能は便利です。ただし、任意の極値で線を引きたい場合があります。これは、Qコマンド を使用して実行できます。

以下のgifは、この関数がどのように機能するかの例を示しています。

任意の極値でトレンドラインを描く例

画面キャプチャアプリには特定の機能があるため、描画する前に毎回チャートをクリックする必要がありました。実際の状況では、チャートをアクティブ化するだけで、必要な数の線を引くことができます。

線は2段階で描画されます。最初の段階では、Qを押す必要があります。任意の線画モードがアクティブになって最初の点がマークされるため、コマンドが実行されたことが明らかになります。

この極値(マーカーがある場所)を使用したくない場合は、もう一度Qを押すと、モードが切り替わって描画がキャンセルされます。(いつか、この動作を変更してEscキーをキャンセルに設定する可能性がありますが、個人的には現在の動作に慣れています)。

最初の点が正しければ、2番目の極値の近くでクリックすることで次の点が選択されます。これが機能すれば、マーカーは不要になるので、削除され、トレンドラインが描画されます。

「任意の」線のパラメータはTモードに依存しないため、たとえば、Tが極値間の間隔の4倍の幅4ピクセルの太い線を描画するように構成できます。 Qは、間隔の2倍の長さの細い線を描画します。

いつものように、コードは複数のファイルに分割されます。

CHARTEVENT_KEYDOWNイベントの処理から、最後からスターを付けましょう。

/* Shortcuts.mqh */

void CShortcuts::OnChartEvent(
  const int id,
  const long &lparam,
  const double &dparam,
  const string &sparam
)
 {
   //...
   
   switch(id)
   {
   
   //...
   
     case CHARTEVENT_KEYDOWN:
      if(CUtilites::GetCurrentOperationChar(Free_Line_Key) == lparam)
       {
        m_graphics.ToggleFreeLineMode();
        if(m_graphics.IsFreeLineMode()){
          m_graphics.DrawFreeLine(CMouse::Bar(),CMouse::Above());
        }
       } 
    
    //...

プログラムがQキーが押されたと判断した場合(文字はFree_Line_Key外部変数に格納されます)、描画モードが切り替わります。モード切り替え後、モードがオンであることが判明した場合、線を引く関数を実行するコマンドが生成されます。

クリックはイベントで処理されます。

/* Shortcuts.mqh */

        //...
        
    case CHARTEVENT_CLICK:
        ChartClick_Handler();
      break;
      
      //...
      
}

//+------------------------------------------------------------------+
//| Processing a click on a free chart field                         |
//+------------------------------------------------------------------+
void CShortcuts::ChartClick_Handler()
 {
  
//---
  if(m_graphics.IsFreeLineMode()){
    m_graphics.DrawFreeLine(
      CMouse::Bar(),CMouse::Above()
    );
  }
  
 }

繰り返しになりますが、キーを押すと、描画が開始される前であっても、描画モードがすぐに切り替わることに注意してください(私の関数の名前はToggleで始まります)。この状態は、キーを使用して再度切り替えるか、線を引いた後も残ります。クリックすると、最初に描画するものがあるかどうかがチェックされ、ある場合は、描画してニュートラルモードに切り替えます。

ChartClick_Handler関数は、チャートのクリックを必要とするモードをさらに追加する予定なので、個別に実装されています。たとえば、前の記事で説明したクロスヘアーや垂直レベルなどの複雑なオブジェクトを削除するモードでは、メニューをキャンセルするためにチャートをクリックする必要がある場合があります。これまでのところ、クリック機能を個別に実装すると、さらなる開発が簡素化されるようです。ただし、これらの機能はすべて後で実装されます。

今のところ、描画がどのように機能するかを引き続き検討していきましょう。

/* Graphics.mqh */


//+------------------------------------------------------------------+
//|  Draws a line by arbitrary specified extrema. In the current     |
//|    implementation, the first extremum is set by a hot key        |
//|    (Q by default), the second is set by clicking near the        |
//|    required top                                                  |
//+------------------------------------------------------------------+
//|  Parameters:                                                     |
//|    int _bar - bar to start search at                             |
//|    bool _isUp - top or bottom?                             |
//|    int _fractalSizeRight - number of bars to the right of extr   |
//|    int _fractalSizeLeft -  number of bars to the left of extremum|
//+------------------------------------------------------------------+
void CGraphics::DrawFreeLine(
  int _bar,
  bool _isUp,
  int _fractalSizeRight=1,
  int _fractalSizeLeft=1
)
 {
//--- Variables
  double    selectedPrice,countedPrice,trendPrice1,trendPrice2;
  datetime  selectedTime,countedTime,trendTime1,trendTime2;
  int       selectedBar,countedBar;
  int       bar1,bar2;

  string trendName="",trendDescription="p2;";
  int fractalForFirstSearch = MathMax(_fractalSizeRight,_fractalSizeLeft)* 2;

//--- Search for a bar that meets the extremum criteria
  selectedBar = CUtilites::GetNearesExtremumSearchAround(
    _bar,
    _isUp,
    _fractalSizeLeft,
    _fractalSizeRight
  );

//--- Building the starting marker
  if(0==m_Clicks_Count)
   {
    m_Clicks_Count=1;
    if(_isUp)
     {
      m_First_Point_Price=iHigh(NULL,PERIOD_CURRENT,selectedBar);
     }
    else
     {
      m_First_Point_Price=iLow(NULL,PERIOD_CURRENT,selectedBar);
     }
    m_First_Point_Time=iTime(NULL,PERIOD_CURRENT,selectedBar);
    //---
    m_First_Point_Time=CUtilites::DeepPointSearch(
                         m_First_Point_Time,
                         _isUp,
                         ENUM_TIMEFRAMES(Period())
                       );
    //---
    DrawFirstPointMarker(_isUp);
   
   }
//--- Processing a click on the chart
  else
   {
    ObjectDelete(0,m_First_Point_Marker_Name);
    if(_isUp)
     {
      countedPrice=iHigh(NULL,PERIOD_CURRENT,selectedBar);
     }
    else
     {
      countedPrice=iLow(NULL,PERIOD_CURRENT,selectedBar);
     }
    countedTime=iTime(NULL,PERIOD_CURRENT,selectedBar);
    //--- Move a point in time on smaller timeframes
    countedTime=CUtilites::DeepPointSearch(countedTime,_isUp,ENUM_TIMEFRAMES(Period()));

    //--- The line is always drawn from left to right. 
    //--- If it is not convenient, you can comment this part
    //---   up to the next comment
    if(countedTime<m_First_Point_Time)
     {
      trendTime1=countedTime;
      trendPrice1=countedPrice;
      trendTime2=m_First_Point_Time;
      trendPrice2=m_First_Point_Price;
     }
    else
     {
      trendTime2=countedTime;
      trendPrice2=countedPrice;
      trendTime1=m_First_Point_Time;
      trendPrice1=m_First_Point_Price;
     }
    //--- Set the description for future correction
    trendDescription+=TimeToString(trendTime2)+";"+DoubleToString(trendPrice2,Digits());

    //selectedPrice=CUtilites::EquationDirect(
    //                trendTime1,trendPrice1,trendTime2,trendPrice2,selectedTime
    //              );
    trendName=CUtilites::GetCurrentObjectName(allPrefixes[0],OBJ_TREND);
    
    TrendCreate(
      0,                    // Chart ID
      trendName,            // Line name
      0,                    // Subwindow number
      trendTime1,           // time of the first point
      trendPrice1,          // price of the first point
      trendTime2,           // time of the second point
      trendPrice2,          // price of the second point
      CUtilites::GetTimeFrameColor(
        CUtilites::GetAllLowerTimeframes()
      ),                    // line color
      Trend_Line_Style,     // line style
      Trend_Line_Width,     // line width
      false,                // background object
      true,                 // is the line selected
      true                  // ray to the right
    );
    
    bar1=iBarShift(NULL,0,trendTime1);
    bar2=iBarShift(NULL,0,trendTime2);
    selectedTime = CUtilites::GetTimeInFuture(
                     //iTime(NULL,PERIOD_CURRENT,0),
                     trendTime1,
                     (int)((bar1-bar2)*m_Free_Trend_Length_Coefficient),
                     COUNT_IN_BARS
                   );
    selectedPrice= ObjectGetValueByTime(0,trendName,selectedTime);
    ObjectSetInteger(0,trendName,OBJPROP_RAY,IsRay());
    ObjectSetInteger(0,trendName,OBJPROP_RAY_RIGHT,IsRay());
    ObjectMove(0,trendName,1,selectedTime,selectedPrice);
    //---
    m_Clicks_Count=0;
    ToggleFreeLineMode();
   }

  ObjectSetString(0,trendName,OBJPROP_TEXT,trendDescription);
  ChartRedraw();
 }

この関数は非常に長いので、後でいくつかの小さな関数に分割する予定です。ハイライトとコメントが、機能を理解するのに役立つことを願っています。

この実装では、関数は両シグナルをチェックします。最初の点の描画が開始されたことを示すイベントと、2番目の点が見つかり、その描画が開始されたことを通知します。これらのイベントを区別するために、m_Clicks_Count変数が導入されました。冒頭の文字「m_」によると、変数がこのクラスに対してグローバルであり、その存続期間がオブジェクトインスタンスの存続期間に等しいことは明らかです。

最初の関数呼び出しの場合(つまり、キーが押された場合)、最初の点を見つけマーカーを描画する必要があります。

2番目の呼び出しの場合は、マーカーを削除し、2番目の点を見つけ線を引く必要があります。これらは主要な5つのブロックでしたが、他のすべてはそれらの実装に必要です。

現在の実装では、将来の価格は直線自体を使用して決定されます。一般に、これはあまり良い考えではありません。描画の時点で、端末は最初に光線を描画する必要があり、次に線の端を目的の点に移動して、光線を描画するかどうかを決定します(外部設定によって異なります)。通常、ライブラリに含まれているIgor Kim(Kim IV)の有名な関数を使用して予備計算を行います。コードのピンクの部分には、この関数のコメント付きの呼び出しがあります。ただし、この場合、点を時間で計算すると、週末に関連するエラーが発生する可能性があるので、避けたいと思います。もちろん、バー番号で行を計算してから、実際の日付に番号を再計算することで、エラーを簡単に回避できます。ただし、現在の実装は私にはより明確に思えます。

したがって、ピンクで強調表示されているコードでは、基本的な極値がすでに検出されています。今やらなければならないのは線を引くことです。まず、2つの基本的な極値の間に線を引きます。ここでは、「ray」プロパティを有効にして、線が未来に引かれるようにする必要があります(このブロックの最初にあるTrendCreate関数)。

設定に基づいて、必要な将来の時間を計算します。

selectedTime = CUtilites::GetTimeInFuture(
                     //iTime(NULL,PERIOD_CURRENT,0),
                     trendTime1,
                     (int)((bar1-bar2)*m_Free_Trend_Length_Coefficient),
                     COUNT_IN_BARS
                   );

次に、標準機能を使用して必要な価格を取得します。

selectedPrice= ObjectGetValueByTime(0,trendName,selectedTime);

その後、線の2番目の点を必要な座標に移動し、実際の線分プロパティを設定するだけです(デフォルトでは、このプロパティはRキー(「Ray」)を使用して切り替えられます 。

線が引かれたら、クリック待機状態をオフにする必要があります。これは次の行で行われます 

    m_Clicks_Count=0;
    ToggleFreeLineMode();

この関数の他のブロックのコードはもう少し複雑です。ここでは、直線の使いやすさを向上させるための便利な機能をいくつか追加しました。

最初の機能は、より短い時間枠でのラインシフト効果に関連しています。通常の方法で線を引くと、時間枠を切り替えるときに次のようなものが表示されます。

D1ラインが終了 H4ラインが終了

D1の極値と正確に一致する線の左端がH4の左にシフトしているため、極値とは一致しません。一日の極値が一日の始まりに当たる必要がないので、これは明らかな効果です。より高い精度が必要な場合は、手動で描画するときに、線をほぼプロットしてから、端を調整するために短い時間枠に切り替えることができます。

この解決策は、1つまたは2つのチャートがある場合に利用できます。20ある場合はどうなるでしょうか。100の場合は?これは煩わしくなりえます。

プログラムには自動描画機能があるので、すべてのオブジェクトを描画するときにこの雑用をプログラムに任せることができます。

これらのアイデアに基づいて、DeepPointSearch関数を作成することにしました。


DeepPointSearch関数

任意の線画関数では、DeepPointSearch関数が点ごとに1回ずつ、2回呼び出されます。この関数は、ユーティリティファイルで利用できます。そのコードは次のとおりです。

//+------------------------------------------------------------------+
//| Search for a given point on lower timeframes                     |
//+------------------------------------------------------------------+
//| Parameters:                                                      |
//|   datetime _neededTime - start time on a higher timeframe        |
//|   bool _isUp - search by highs or by lows                        |
//|   ENUM_TIMEFRAMES _higher_TF - the highest period                |
//+------------------------------------------------------------------+
//| Return value:                                                    |
//|   More accurate date (on the lowest possible timeframe)          |
//+------------------------------------------------------------------+
datetime CUtilites::DeepPointSearch(
  datetime _neededTime,
  bool _isUp,
  ENUM_TIMEFRAMES _higher_TF=PERIOD_CURRENT
)
 {
//---
  //--- As a result it gets the most accurate time available
  datetime deepTime=0;
  //--- current timeframe
  ENUM_TIMEFRAMES currentTF;
  //--- The number of the highest timeframe in the list of all available periods
  int highTFIndex = GetTimeFrameIndexByPeriod(_higher_TF); 
  //--- The higher period in seconds
  int highTFSeconds = PeriodSeconds(_higher_TF);
  //--- Current interval in seconds
  int currentTFSeconds;
  //--- Counter
  int i;
  //--- Bar number on a higher timeframe
  int highBar=iBarShift(NULL,_higher_TF,_neededTime);
  //--- Bar number on the current timeframe
  int currentBar;
  //--- The total number of bars on the current timeframe
  int tfBarsCount;
  //--- How many bars of a lower TF fit into one bar of a higher TF
  int lowerBarsInHigherPeriod;
  //--- Maximum allowed number of bars in the terminal
  int terminalMaxBars = TerminalInfoInteger(TERMINAL_MAXBARS);

//--- Loop sequentially through all timeframes
  for(i=0; i<highTFIndex; i++)
   {
    //--- Get a timeframe by a number in the list
    currentTF=GetTimeFrameByIndex(i);
//--- Check if this timeframe has the required time.
    tfBarsCount=iBars(NULL,currentTF);
    if(tfBarsCount>terminalMaxBars-1)
     {
      tfBarsCount=terminalMaxBars-1;
     }
    deepTime=iTime(NULL,currentTF,tfBarsCount-1);
//--- If it has, find it.
    if(deepTime>0 && deepTime<_neededTime)
     {
      currentTFSeconds=PeriodSeconds(currentTF);
      
      //--- Search for the required bar only within the higher TF candlestick
      lowerBarsInHigherPeriod=highTFSeconds/currentTFSeconds;
      currentBar = iBarShift(NULL,currentTF,_neededTime);
      
      if(_isUp)
       {
        currentBar = iHighest(
                       NULL,currentTF,MODE_HIGH,
                       lowerBarsInHigherPeriod+1,
                       currentBar-lowerBarsInHigherPeriod+1
                     );

       }
      else
       {
        currentBar = iLowest(
                       NULL,currentTF,MODE_LOW,
                       lowerBarsInHigherPeriod+1,
                       currentBar-lowerBarsInHigherPeriod+1
                     );
       }
      deepTime=iTime(NULL,currentTF,currentBar);
      //--- Once the required time is found, stop the search
      break;
     }
   }
//--- If reached the end of the loop
  if(i==highTFIndex)
   {
    //--- then the required time is only available on the higher timeframe.
    deepTime=_neededTime;
   }
//---
  return (deepTime);
 }

私にとっての主な難しさは、メイン検索フラグメントがどのように機能するかを理解することでした。当然、まず行うことは、必要な時間が履歴に存在するかどうかを判断することです。ご存知かもしれませんが、より短い時間枠には、より長い時間枠で利用可能な情報の一部が含まれていないことがよくあります。標準のiBars関数では履歴内のバーの数を計算しますが、ターミナルでは限られた数のバーしか表示できないため、これでは不十分です。まず、次のコードを使用して、ターミナルが表示できるバーの数を調べます。

//--- Maximum allowed number of bars in the terminal
  int terminalMaxBars = TerminalInfoInteger(TERMINAL_MAXBARS);

履歴に含まれるバーが多すぎる場合は、表示されているバーに制限します。

次に、iTime関数を使用して、履歴の最後の小節の時刻を定義します。この時間が希望の時間よりも長い場合は、利用可能な最高の日付が最新の日付であるため、さらに調べる意味はありません。次のより長いTFに切り替えるだけです。ターミナルで利用可能な最後のローソク足が私たちが探しているものよりも早い場合、この点がまだ意味のある最も深い場所を見つけた可能性があります。

ルーチンは、すべてのチェックの後に開始されます。必要な点は、より長いTFのローソク足の範囲内で最も極端な点になります。分析するローソク足の数を決定するだけで済みます。その後、標準関数が最も極端な極値を決定するのを支援します。これに基づいて時間を計算し、作業を終了できます。

ライブラリの現在の実装では、この関数はTキーとQキーによって呼び出される行にのみ適用されます。ただし、次のバージョンでは、この機能はすべての商品で使用できるようになります。さらに、商品ごとに個別にカスタマイズできるようにする予定です。


時間補正

この実装の2番目の特定の機能は、時間による行の修正です。以下のアニメーションは問題を説明しています。

最後の長方形の収縮に注意してください。真ん中の長方形から1日以上離れていた線の端は、それに非常に近いことがわかりました。したがって、上記の点もシフトしました(上部近くの直線の動作に注意してください)。線が縮小すると、取引ストラテジーに影響を与える可能性のある新しいブレイクアウトが表示されます。

これは、たとえば週に1回スパイクが発生する可能性がある外国為替市場にとってはそれほど重要ではないかもしれませんが、株式市場では、取引所によってはこのような時間のギャップが毎日発生する可能性があり、1日以内に発生することがよくあります。

ここで自動化が役に立ちます。

パーツを希望どおりに機能させるには、「正しい」座標を保存してから、必要に応じて調整します。

ほとんどのトレーダーは自動オブジェクトを作成するときに説明を使用しないため、座標を保存するために直線の説明を選択しました。オプションで、行が多すぎる場合は、行のリストまたはターミナルグローバル変数を含むファイルを使用できます。

/* Graphics.mqh */

void CGraphics::DrawFreeLine(//...)
 {

//...
  string trendDescription="p2;";

//...
  trendDescription+=TimeToString(trendTime2)+";"+DoubleToString(trendPrice2,Digits());
  
//...
  ObjectSetString(0,trendName,OBJPROP_TEXT,trendDescription);

次に、前述のアクションを「物理的な」線の座標に適用します。以下のコードは非常に明確だと思います。

/* Utilites.mqh */

//+------------------------------------------------------------------+
//|  Adjusts the position of line end in the future in case of price |
//|   gaps                                                           |
//+------------------------------------------------------------------+
//| Parameters:                                                      |
//|   string _line_name - the name of the line to be corrected       |
//+------------------------------------------------------------------+
void CUtilites::CorrectTrendFutureEnd(string _line_name)
 {
//---
  if(ObjectFind(0,_line_name)<0)
   {
    PrintDebugMessage(__FUNCTION__+" _line_name="+_line_name+": Object does not exist");
    //--- If there is no object to search, there is nothing more to do.
    return;
   }
  //--- Get a description
  string line_text=ObjectGetString(0,_line_name,OBJPROP_TEXT);
  
  string point_components[]; // array for point description fragments
  string name_components[];  // array containing line name fragments
  string helpful_name="Helpful line"; // the name of the auxiliary line
  string vertical_name=""; // the name of the corresponding vertical from the crosshair
  
  //--- Get the point time and price in string form
  int point_components_count=StringSplit(line_text,StringGetCharacter(";",0),point_components);
  
  datetime time_of_base_point; // time of the basic point
  datetime time_first_point,time_second_point; // the time of the first and the second point
  datetime time_far_ideal; // estimated time in the future
  double price_of_base_point; // the price of the basic point
  double price_first_point,price_second_point; // the prices of the first and the second point
  int i; // counter

//--- Check if the line is needed
  if(line_text=="" || point_components_count<3 || point_components[0]!="p2")
   {
    PrintDebugMessage(__FUNCTION__+" Error: the line cannot be used");
    return;
   }
//--- Get the coordinates of the "basic" point from the line description
  time_of_base_point=StringToTime(point_components[1]);
  price_of_base_point=StringToDouble(point_components[2]);
  if(time_of_base_point==0 || price_of_base_point==0)
   {
    PrintDebugMessage(__FUNCTION__+" Error: Unusable description");
    return;
   }
//--- Get the real coordinates of the line
  time_first_point = (datetime)ObjectGetInteger(0,_line_name,OBJPROP_TIME,0);
  time_second_point = (datetime)ObjectGetInteger(0,_line_name,OBJPROP_TIME,1);
  price_first_point = ObjectGetDouble(0,_line_name,OBJPROP_PRICE,0);
  price_second_point = ObjectGetDouble(0,_line_name,OBJPROP_PRICE,1);

//--- Create an auxiliary line (from the starting point to the base one)
  MakeHelpfulLine(
    time_first_point,
    price_first_point,
    time_of_base_point,
    price_of_base_point
  );

//--- Calculate the correct time for the current situation
  time_far_ideal=ObjectGetTimeByValue(0,helpful_name,price_second_point);
//---
  if(time_second_point != time_far_ideal)
   {
    //--- move the free end of the trend line
    ObjectMove(0,_line_name,1,time_far_ideal,price_second_point);
    //--- and the corresponding vertical
    StringSplit(_line_name,StringGetCharacter("_",0),name_components);
    for(i=0; i<ObjectsTotal(0,-1,OBJ_VLINE); i++)
     {
      vertical_name = ObjectName(0,i,-1,OBJ_VLINE);
      if(name_components[0]==StringSubstr(vertical_name,0,StringFind(vertical_name,"_",0)))
       {
        if((datetime)ObjectGetInteger(0,vertical_name,OBJPROP_TIME,0)==time_second_point)
         {
          ObjectMove(0,vertical_name,0,time_far_ideal,price_second_point);
          break;
         }
       }
     }
   }
  // Delete the auxiliary line
  RemoveHelpfulLine();
 }

このコードは一定の間隔で呼び出す必要があります。新しい時間の始まりに設定しました。

/* Shortcuts.mq5 */

int OnCalculate(/*...*/)
 {
   //...
   if(CUtilites::IsNewBar(First_Start_True,PERIOD_H1))
   {
    for(i=0; i<all_lines_count; i++)
     {
      line_name=ObjectName(0,i,-1,OBJ_TREND);
      CUtilites::CorrectTrendFutureEnd(line_name);
      ChartRedraw();
     }
   }
   //...
 }


現在のライブラリ実装で使用されているキー

アクション
 キー 説明
 メインのTFの時間枠を長くする(TFパネルから)  U  Up
 時間枠を短くする  D  Down
 チャートのZレベルを変える(チャートが他のすべてのオブジェクトの上になるかどうか)  Z  Z order
 マウスに最も近い2つの一方向の極値点に基づいて傾斜したトレンドラインを描画する  T  Trend line
 新しい線を線分モードに切り替える
 R  Ray
 単純な縦線を描画する
 I(i) [縦のみ可視]
 単純な横線を描画する
 H  Horizontal
 アンドリュースピッチフォークセットを描画する
 P  Pitchfork
 フィボナッチファン(VFun)を描画する
 F key  Fun
 短い水平レベルを描画する
 S  Short
 拡張した水平レベルを描画する
 L key  Long
 レベルマーク付きの縦線を描画する
 V  Vertical
 クロスヘアーを描画する
 X  [クロスのみ可視]
 任意の極値によって線を引く
 Q  [適合性なし...「L」と「T」は自由でない]
 長方形のグループを描画する
 B  Box


終わりに

この資料がお役に立てば幸いです。コメントや改善のアイデアがあれば、記事へのコメントで共有してください。

さらに、厳密な極値だけでなく、接線にも線を引く機能を実装する予定です。

また、チャンネル用に何かを実装したいと思います。今は等距離チャネルでの作業についてのみを考えていますが、コメントやPMで、ライブラリの原則に従う何か他のものの描画の提案があった場合、そのような提案を検討します。

さらなる改善として、後で(入力変数を介した指標設定の代わりに、または一緒に)ファイルに設定を保存し、フローの設定を変更できるようにするためのグラフィカルインターフェイスを追加します。


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

添付されたファイル |
より優れたプログラマー(第06部): 効果的なコーディングにつながる9つの習慣 より優れたプログラマー(第06部): 効果的なコーディングにつながる9つの習慣
効果的なコーディングにつながるのはコードを書くことだけではありません。経験を通して見つけた、効果的なコーディングにつながる特定の習慣があります。この記事では、そのいくつかについて詳しく説明します。これは、複雑なアルゴリズムをより手間をかけずに作成する能力を向上させたいすべてのプログラマーにとって必読の記事です。
MQL5クックブック - 経済指標カレンダー MQL5クックブック - 経済指標カレンダー
この記事では、経済指標カレンダーのプログラミング機能に焦点を当て、カレンダーのプロパティに簡単にアクセスしてイベント値を受け取るためのクラスの作成について考察し検討します。実用的な例として役立つように、CFTCの投機筋ネットポジションを使用して指標を開発します。
時間の取扱い(第2部): 関数 時間の取扱い(第2部): 関数
証券会社のオフセットとGMTを自動で特定します。おそらく不十分な答えしかくれない(欠如した時間について説明することはいとわないでしょうが)証券会社にサポートを求める代わりに、時間が変わる週に証券会社が価格をどのように計算するかを自分で見ます。結局のところ、私たちはPCを持っているので、面倒な手作業ではなくプログラムを使用します。
EAコンストラクタの開発の試み EAコンストラクタの開発の試み
この記事では、既製のEAの形で一連の取引機能を提供します。この方法では、指標を追加して入力を変更するだけで、複数の取引ストラテジーを取得できます。