ローソク足分析技術の研究(第4部): パターンアナライザーの更新と追加

Alexander Fedosov | 24 7月, 2019

目次

はじめに

この連載のこれまでの記事では、既存のローソク足パターンの関連性をテストするMetaTrader 5アプリを作成しました。第2部では、ショートローソク足、ロングローソク足、同事、コマ(極線)などの単純なローソク足タイプに基づいてカスタムパターンを作成することが可能になりました。第3部では、ローソク足パターンに基づいて指標とエキスパートアドバイザーを作成するためのライブラリを開発しました。

本稿では、パターンアナライザーアプリケーションの新しいバージョンについて説明します。このバージョンでは、バグ修正と新機能、そして改訂されたユーザーインターフェイスが提供されています。新しいバージョンを開発するときに、前の記事からのコメントと提案が考慮されました。結果として得られたアプリケーションは、本稿で説明されています。

更新の概要

ユーザーインターフェイスは、あらゆるアプリケーションの重要な部分です。適切に準備されたインターフェイス構造により、アプリケーションはより効率的に使用されるようになります。新しいアプリケーションの外観を以前のものと比較します。[Analysis]タブから始めましょう。なぜ改善が必要だったのでしょうか。

図1 前のバージョンの[Analysis]タブのインターフェイス

ポイント1: タブの配置と寸法

図1では、1とマークされているタブがウィンドウ上部にあります。ここの右上の部分は空で使用されていませんが、この部分はタブを追加するのに十分ではありません。テキストのフォントが小さすぎます。これら3つのタブはウィンドウの左側に移動され、垂直に配置されて見やすくなりました。さらに、セクションを追加するための追加のスペースがあります。

ポイント2: パターンテストの結果を含む表

この視覚データの提示はあまり効率的ではなかったので、フォント、行の高さ、表サイズを大きくして、読みやすくしました。

ポイント3: 現在の時間枠の選択

すべてのパターンの選択構造([Timeframe] -> [Result])はテスト結果の視覚的表示を制限します。これを改善するために、分析されたパターンの個々の選択と同様に、複数時間枠選択オプションを開発します。これにより、パターンの使用をより柔軟にカスタマイズできます。 

ポイント4: サンプリング範囲

前のバージョンで実装されたアイデアは、現在のデータから履歴の中で特定の数のローソク足までの範囲でテストすることでした。ある日付から別の日付までのより具体的な選択はできませんでした。そのため、範囲選択方法を見直します。以下の図2は、上記のすべての問題と考えられる改善点の解決方法を示しています。

図2 [Analysis]タブインターフェイスの更新

上記の点に対する解決策は次のとおりです。

アプリケーションウィンドウは、新しい要素を表示するために大きくなりました。もう1つの重要な新機能は、ポイント単位の[Trend threshold value]パラメータ(図3)が[Settings]タブから[Analyze]タブと[AutoSearch]タブの両方に移動したことです。設定はタブごとに異なります。 


図3 [Trend Threshold]パラメータの新しい位置

最後に変更された要素は、結果表の構造です。発生数の列が削除され、より関連性の高い[Timeframe]パラメータが代わりに追加されました。 

図4 結果表の新しい構造

それでは、生成されたパターンを処理する2番目の[AutoSearch]タブの改善点を見てみましょう。

ポイント1: 異なるタブでの設定

[AutoSearch]セクションに直接関連する設定は[Setting]タブにあります。そのため、設定を変更するには、[AutoSearch]タブと[Setting]タブを常に切り替える必要があります。これが、ほとんどすべての設定が[AutoSearch]タブに移動された理由です。トレンドしきい値、現在の時間枠と日付範囲の選択に関しては、さらなる改善が行われました。[AutoSearch]タブの更新は図5に示されています。

図5 [AutoSearch]機能の更新 

 これにより、パターンをより便利に扱うことができます。このタブの日付範囲も個別です。


更新の実装

上記の更新の実装と計算の変更をより詳細に検討しましょう。

アプリケーションウィンドウの構造: メインウィンドウ作成メソッド

グラフィカルインタフェースの作成を担当するCProgram::CreateGUI()メソッドが補足されました。

//+------------------------------------------------------------------+
//| プログラムのグラフィカルインターフェイスを作成する                     |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- パネルの作成
   if(!CreateWindow("Pattern Analyzer"))
      return(false);
//--- ダイアログウィンドウの作成
   if(!CreateWindowSetting1("Settings"))
      return(false);
//--- ダイアログウィンドウの作成
   if(!CreateWindowSetting2("Date range settings"))
      return(false);
//--- ダイアログウィンドウの作成
   if(!CreateWindowSetting3("Date range settings"))
      return(false);
//--- GUI 作成を完成する
   CWndEvents::CompletedGUI();
   return(true);
  }
//+-----------------------------------------------------------------

CreateWindowSetting2()およびCreateWindowSetting3()は、図1に示す新しい日付範囲選択ツールの表示を担当しています。メインアプリウィンドウ作成メソッド(CreateWindow())も再設計され、各タブのUI要素に対応する3つのブロック(分析、自動検索、設定)に分割されました。

//+------------------------------------------------------------------+
//| The Analyze tab                                                  |
//+------------------------------------------------------------------+
//--- Create buttons of the pattern set
   if(!CreatePatternSet(m_patterns,10,10))
      return(false);
//--- Timeframe headers
   if(!CreateTFLabel(m_text_labels[1],10,100,0))
      return(false);
//--- Create buttons of the timeframe set
   if(!CreateTimeframeSet(m_timeframes,10,125,0))
      return(false);
//--- Symbol filter search window 
   if(!CreateSymbolsFilter(m_symb_filter1,m_request1,10,180,0))
      return(false);
//--- Create a button for date range selection
   if(!CreateDateRange(m_request3,280,180,0))
      return(false);
//--- Create an entry field for the threshold profit value
   if(!CreateThresholdValue(m_threshold1,400,180,100,0))
      return(false);
//--- Create a table of symbols
   if(!CreateSymbTable(m_symb_table1,10,225,0))
      return(false);
//--- Create a table of results
   if(!CreateTable1(m_table1,120,225,0))
      return(false);

最初のタブに、新しいインターフェイス要素を表示するためのメソッドが追加されました。 

//+------------------------------------------------------------------+
//| The AutoSearch tab                                               |
//+------------------------------------------------------------------+
   if(!CreateTFLabel(m_text_labels[4],10,10,1))
      return(false);
//--- ボタン
   if(!CreateDualButton(m_buttons[6],m_buttons[7],200,50))
      return(false);
   if(!CreateTripleButton(m_buttons[8],m_buttons[9],m_buttons[10],10,50))
      return(false);
//--- Timeframe headers
   if(!CreateTFLabel(m_text_labels[5],10,100,1))
      return(false);
//--- Create buttons of the timeframe set
   if(!CreateTimeframeSet(m_timeframes1,10,125,1))
      return(false);
//--- フィールドを編集する
   if(!CreateSymbolsFilter(m_symb_filter2,m_request2,10,180,1))
      return(false);
//--- Create a button for date range selection
   if(!CreateDateRange(m_request4,280,180,1))
      return(false);
//--- Create an entry field for the threshold profit value
   if(!CreateThresholdValue(m_threshold2,400,180,100,1))
      return(false);
//--- Create a table of symbols
   if(!CreateSymbTable(m_symb_table2,10,225,1))
      return(false);
//--- Create a table of results
   if(!CreateTable2(m_table2,120,225,1))
      return(false);

また、生成されたパターンサイズの選択に関連する要素の表示を担当するメソッドCreateTripleButton()と、 CreateDualButton()メソッドの[Repeat/No repeat]切り替え可能オプション以下のメソッドが[Setting]タブから2番目の[AutoSearch]タブに移動されました(図5)。 時間枠ヘッダとその選択を担当するメソッドが追加されました。

//+------------------------------------------------------------------+
//| The Settings tab                                                 |
//+------------------------------------------------------------------+
//--- Creating candlestick settings
   if(!CreateCandle(m_pictures[0],m_buttons[0],m_candle_names[0],"Long",10,10,"Images\\EasyAndFastGUI\\Candles\\long.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[1],m_buttons[1],m_candle_names[1],"Short",104,10,"Images\\EasyAndFastGUI\\Candles\\short.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[2],m_buttons[2],m_candle_names[2],"Spinning top",198,10,"Images\\EasyAndFastGUI\\Candles\\spin.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[3],m_buttons[3],m_candle_names[3],"Doji",292,10,"Images\\EasyAndFastGUI\\Candles\\doji.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[4],m_buttons[4],m_candle_names[4],"Marubozu",386,10,"Images\\EasyAndFastGUI\\Candles\\maribozu.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[5],m_buttons[5],m_candle_names[5],"Hammer",480,10,"Images\\EasyAndFastGUI\\Candles\\hammer.bmp"))
      return(false);
//--- Text labels
   if(!CreateTextLabel(m_text_labels[0],10,140))
      return(false);
   if(!CreateTextLabel(m_text_labels[3],300,140))
      return(false);
//--- フィールドを編集する
   if(!CreateCoef(m_coef1,10,180,"K1",1))
      return(false);
   if(!CreateCoef(m_coef2,100,180,"K2",0.5))
      return(false);
   if(!CreateCoef(m_coef3,200,180,"K3",0.25))
      return(false);
   if(!CreateLanguageSetting(m_lang_setting,10,240,2))
      return(false);
//--- List views
   if(!CreateListView(300,180))
      return(false);
//---
   if(!CreateCheckBox(m_checkbox1,300+8,160,"All candlesticks"))
      return(false);
//--- Status Bar
   if(!CreateStatusBar(1,26))
      return(false);

[Setting]セクションに含まれる要素が少なくなりました。これには、個々のローソク足の設定確率係数と効率係数の計算のための比率の設定[AutoSearch]タブでのインターフェイス言語の選択、パターンの生成のためのローソク足の選択が含まれます。次に、新しいメソッドをさらに詳しく検討します。 

パターン選択を作成するためのメソッドCreatePatternSet(): 分析のために既存のパターンを選択するための切り替え可能なボタンのセットです。

図6 分析のためのパターン選択ツールの原理

実装は以下のとおりです。

//+------------------------------------------------------------------+
//| Creates a set of pattern buttons                                 |
//+------------------------------------------------------------------+
bool CProgram::CreatePatternSet(CButton &button[],int x_gap,int y_gap)
  {
   ArrayResize(button,15);
   string pattern_names[15]=
     {
      "Hummer",
      "Invert Hummer",
      "Handing Man",
      "Shooting Star",
      "Engulfing Bull",
      "Engulfing Bear",
      "Harami Cross Bull",
      "Harami Cross Bear",
      "Harami Bull",
      "Harami Bear",
      "Doji Star Bull",
      "Doji Star Bear",
      "Piercing Line",
      "Dark Cloud Cover",
      "All Patterns"
     };
   int k1=x_gap,k2=x_gap,k3=x_gap;
   for(int i=0;i<=14;i++)
     {
      if(i<5)
        {
         CreatePatternButton(button[i],pattern_names[i],k1,y_gap);
         k1+=150;
        }
      else if(i>=5 && i<10)
        {
         CreatePatternButton(button[i],pattern_names[i],k2,y_gap+30);
         k2+=150;
        }
      else if(i>=10 && i<14)
        {
         CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60);
         k3+=150;
        }
      else if(i==14)
        {
         CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60);
        }
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Creates a button for selecting a pattern for analysis            |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Candles\\passive.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\pressed.bmp"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreatePatternButton(CButton &button,const string candlename,const int x_gap,const int y_gap)
  {
//--- メインコントロールへのポインタを保存する
   button.MainPointer(m_tabs1);
//--- タブに取り付ける
   m_tabs1.AddToElementsArray(0,button);
//--- プロパティ
   button.XSize(120);
   button.YSize(20);
   button.Font("Trebuchet");
   button.FontSize(9);
   button.LabelColor(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.IsCenterText(true);
   button.TwoState(true);
   button.IconFile("Images\\EasyAndFastGUI\\Candles\\passive.bmp");
   button.IconFilePressed("Images\\EasyAndFastGUI\\Candles\\pressed.bmp");
//--- コントロールを作成する
   if(!button.CreateButton(candlename,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

すべてのパターンを選択/選択解除する最後のボタン[All Patterns]に注目してください。ボタン押下は、ボタン押下イベント処理セクションの追加コードによって処理されます。

   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Select and unselect all pattern buttons
      if(lparam==m_patterns[14].Id())
        {
         if(m_patterns[14].IsPressed())
           {
            for(int i=0;i<14;i++)
               m_patterns[i].IsPressed(true);
           }
         else if(!m_patterns[14].IsPressed())
           {
            for(int i=0;i<14;i++)
               m_patterns[i].IsPressed(false);
           }
         for(int i=0;i<14;i++)
            m_patterns[i].Update(true);
        }
...
}

現在の時間枠選択選択メソッド(CreateTimeframeSet())は、前のものと非常によく似ており、分析のための時間枠を選択する切替可能なボタンのセットを持っています。

図7 分析のための時間枠選択ツールの原理

実装のコードは以下のとおりです。

//+------------------------------------------------------------------+
//| Creates a set of timeframe buttons                               |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeSet(CButton &button[],int x_gap,int y_gap,const int tab)
  {
   ArrayResize(button,22);
   string timeframe_names[22]=
     {"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN","ALL"};
   int k1=x_gap,k2=x_gap;
   for(int i=0;i<22;i++)
     {
      CreateTimeframeButton(button[i],timeframe_names[i],k1,y_gap,tab);
      k1+=33;
     }
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeButton(CButton &button,const string candlename,const int x_gap,const int y_gap,const int tab)
  {
//--- メインコントロールへのポインタを保存する
   button.MainPointer(m_tabs1);
//--- タブに取り付ける
   m_tabs1.AddToElementsArray(tab,button);
//--- プロパティ
   button.XSize(30);
   button.YSize(30);
   button.Font("Trebuchet");
   button.FontSize(10);
   button.LabelColor(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.BackColor(C'200,200,200');
   button.BackColorHover(C'200,200,200');
   button.BackColorPressed(C'50,180,75');
   button.BorderColor(C'200,200,200');
   button.BorderColorHover(C'200,200,200');
   button.BorderColorPressed(C'50,180,75');
   button.IsCenterText(true);
   button.TwoState(true);
//--- コントロールを作成する
   if(!button.CreateButton(candlename,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

また、すべての時間枠を選択/選択解除するためのボタンもあり、ボタン押下処理セクションで処理されます。

//--- Select and unselect all pattern buttons
      if(lparam==m_timeframes[21].Id())
        {
         if(m_timeframes[21].IsPressed())
           {
            for(int i=0;i<21;i++)
               m_timeframes[i].IsPressed(true);
           }
         else if(!m_timeframes[21].IsPressed())
           {
            for(int i=0;i<21;i++)
               m_timeframes[i].IsPressed(false);
           }
         for(int i=0;i<21;i++)
            m_timeframes[i].Update(true);
        }

次の新しいアイテムは[Date Range]ボタンです。これは新しいコンポジットサンプル範囲設定ツールの一部で、CreateDateRange()メソッドを使用して実装されています。

図8 分析のための日付範囲選択の原則

実装は以下のとおりです。

//+------------------------------------------------------------------+
//| Creates a button to show the date range selection window         |
//+------------------------------------------------------------------+
bool CProgram::CreateDateRange(CButton &button,const int x_gap,const int y_gap,const int tab)
  {
//--- メインコントロールへのポインタを保存する
   button.MainPointer(m_tabs1);
//--- タブに取り付ける
   m_tabs1.AddToElementsArray(tab,button);
//--- プロパティ
   button.XSize(100);
   button.YSize(25);
   button.Font("Trebuchet");
   button.FontSize(10);
   button.IsHighlighted(false);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
//--- コントロールを作成する
   if(!button.CreateButton("",x_gap,y_gap))
      return(false);
//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

ボタン押下イベントハンドラには、日付範囲を含むダイアログボックスの表示を担当するコードも含まれています。

      //---
      if(lparam==m_request3.Id())
        {
         int x=m_request3.X();
         int y=m_request3.Y()+m_request3.YSize();
         m_window[2].X(x);
         m_window[2].Y(y);
         m_window[2].OpenWindow();
         val=(m_lang_index==0)?"Настройки диапазона дат":"Date Range Settings";
         m_window[2].LabelText(val);
        }

タブに追加された新しい要素は、座標パラメータを除いて[Analyze]タブ要素の実装に似ているため、記述する必要はありません。他の新しいウィンドウの表示を担当するメソッドを考察しましょう。 

アプリケーションウィンドウの構造: アプリケーションダイアログボックス作成メソッド

[Analyze]タブと[AutoSearch]タブのダイアログボックスを表示するメソッドは似ているため、そのうちの1つを検討します。

//+------------------------------------------------------------------+
//| Creates a date range selection dialog in the Analyze tab         |
//+------------------------------------------------------------------+
bool CProgram::CreateWindowSetting2(const string caption_text)
  {
//--- ウィンドウ配列へのポインタを追加する
   CWndContainer::AddWindow(m_window[2]);
//--- 座標
   int x=m_request3.X();
   int y=m_request3.Y()+m_request3.YSize();
//--- プロパティ
   m_window[2].XSize(372);
   m_window[2].YSize(300);
   m_window[2].WindowType(W_DIALOG);

//--- フォームを作成する
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   if(!CreateCalendar(m_calendar1,m_window[2],10,25,D'01.01.2018',2))
      return(false);
   if(!CreateCalendar(m_calendar2,m_window[2],201,25,m_calendar2.Today(),2))
      return(false);
//---
   if(!CreateTimeEdit(m_time_edit1,m_window[2],10,200,"Time",2))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[2],200,200,"Time",2))
      return(false);
//---
   return(true);
  }


計算部分ローソク足とパターンファインダーのメソッドを再設計しました。

ユーザインターフェイス構造の大幅な変更、および新しい要素の追加といくつかの古い要素の削除により、計算方法も変更されました。現在アプリケーションでは2つの計算メソッドが利用可能です。1つ目は既存のパターンに使用され、2つ目は生成されたパターンに使用されます。 

[Symbols]表で利用可能な取引商品の1つをクリックすると計算が開始されます。この規則は、[Analyze]と[AutoSearch]の両方のタブに適用されます。タブに応じて、2つのメソッドのいずれかが呼び出されます。

//+------------------------------------------------------------------+
//| Symbol change in the Analyze tab                                 |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol1(const long id)
  {
//--- 要素IDを確認する
   if(id!=m_symb_table1.Id())
      return(false);
//--- Exit if the line is not selected
   if(m_symb_table1.SelectedItem()==WRONG_VALUE)
     {
      //--- ステータスバーに完全な銘柄説明を表示する
      m_status_bar.SetValue(0,"No symbol selected for analysis");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- 銘柄を取得する
   string symbol=m_symb_table1.GetValue(0,m_symb_table1.SelectedItem());
//--- ステータスバーに完全な銘柄説明を表示する
   string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
//---
   GetPatternType(symbol);
   return(true);
  }
//+------------------------------------------------------------------+
//| Symbol change in the AutoSearch tab                              |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol2(const long id)
  {
//--- 要素IDを確認する
   if(id!=m_symb_table2.Id())
      return(false);
//--- Exit if the line is not selected
   if(m_symb_table2.SelectedItem()==WRONG_VALUE)
     {
      //--- ステータスバーに完全な銘柄説明を表示する
      m_status_bar.SetValue(0,"No symbol selected for analysis");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- 銘柄を取得する
   string symbol=m_symb_table2.GetValue(0,m_symb_table2.SelectedItem());
//--- ステータスバーに完全な銘柄説明を表示する
   string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
//---
   if(!GetCandleCombitation())
     {
      if(m_lang_index==0)
         MessageBox("Число выбранных свечей меньше размера исследуемого паттерна!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("The number of selected candles is less than the size of the studied pattern!","Error",MB_OK);
      return(false);
     }
//---
   GetPatternType(symbol,m_total_combination);
   return(true);
  }

それぞれのメソッドの終わりでGetPattertType()メソッドが2つの異なる引数タイプで呼び出されます。これは、パターンの検索および取得結果の処理における重要なメソッドです。それでは、それぞれのメソッドについて詳細に検討しましょう。

最初のメソッドタイプは、既存のパターンを検索するために使用されます。

   bool              GetPatternType(const string symbol);

このメソッドの実装は非常に長いので、ここでは1つのパターンだけの例を示します。

//+------------------------------------------------------------------+
//| Pattern recognition                                              |
//+------------------------------------------------------------------+
bool CProgram::GetPatternType(const string symbol)
  {
   CANDLE_STRUCTURE cand1,cand2;
//---
   RATING_SET hummer_coef[];
   RATING_SET invert_hummer_coef[];
   RATING_SET handing_man_coef[];
   RATING_SET shooting_star_coef[];
   RATING_SET engulfing_bull_coef[];
   RATING_SET engulfing_bear_coef[];
   RATING_SET harami_cross_bull_coef[];
   RATING_SET harami_cross_bear_coef[];
   RATING_SET harami_bull_coef[];
   RATING_SET harami_bear_coef[];
   RATING_SET doji_star_bull_coef[];
   RATING_SET doji_star_bear_coef[];
   RATING_SET piercing_line_coef[];
   RATING_SET dark_cloud_cover_coef[];
//--- Receive data for selected timeframes
   GetTimeframes(m_timeframes,m_cur_timeframes1);
   int total=ArraySize(m_cur_timeframes1);
//--- Check at least one selected timerame
   if(total<1)
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      return(false);
     }
   int count=0;
   m_total_row=0;
   m_table_number=1;
//--- すべての行を削除する
   m_table1.DeleteAllRows();
//--- Get the date range
   datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   datetime end=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00");
//--- Check specified dates
   if(start>end || end>TimeCurrent())
     {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return(false);
     }
//--- 唐笠(強気)
   if(m_patterns[0].IsPressed())
     {
      ArrayResize(m_hummer_total,total);
      ArrayResize(hummer_coef,total);
      ZeroMemory(m_hummer_total);
      ZeroMemory(hummer_coef);
      ZeroMemory(cand1);
      count++;
      //--- Calculation by timeframes
      for(int j=0;j<total;j++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         int copied=CopyRates(symbol,m_cur_timeframes1[j],start,end,rt);
         for(int i=0;i<copied;i++)
           {
            GetCandleType(symbol,cand1,m_cur_timeframes1[j],i);             // Current candlestick
            if(cand1.trend==DOWN &&                                        // Checking the trend direction
               cand1.type==CAND_HAMMER)                                    // Checking the "Hammer"
              {
               m_hummer_total[j]++;
               GetCategory(symbol,i+3,hummer_coef[j],m_cur_timeframes1[j],m_threshold_value1);
              }
           }
         AddRow(m_table1,"Hammer",hummer_coef[j],m_hummer_total[j],m_cur_timeframes1[j]);
        }
     }
...
//---
   if(count>0)
     {
      //---
      m_table1.DeleteRow(m_total_row);
      //--- 表を更新する
      m_table1.Update(true);
      m_table1.GetScrollVPointer().Update(true);
     }
   else
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not chosen a pattern!","Error",MB_OK);
     }
   return(true);
  }

演算アルゴリズムは以下のように行われます。

次に、以前のバージョンに存在していて更新されたバージョンで変更されたメソッドと得られたデータを計算するための1つの新しいメソッド、および結果表でのそれらの出力について検討します。

パターンを検索するGetPatternType()メソッドには2つの異なる実装がありますが、これら3つのメソッドは汎用的です。詳しく見てみましょう。

//+------------------------------------------------------------------+
//| ローソク足タイプの特定                                              |
//+------------------------------------------------------------------+
bool CProgram::GetCandleType(const string symbol,CANDLE_STRUCTURE &res,ENUM_TIMEFRAMES timeframe,const int shift)
  {
   MqlRates rt[];
   int aver_period=5;
   double aver=0.0;
   datetime start=TimeCurrent();
   SymbolSelect(symbol,true);
   //--- Get the start date from the range depending on the type of patterns
   if(m_table_number==1)
      start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   else if(m_table_number==2)
      start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
//--- Shift date 
   start+=PeriodSeconds(timeframe)*shift;
   int copied=CopyRates(symbol,timeframe,start,aver_period+1,rt);
   if(copied<6)
     {
      Print(start,": Not enough data for calculation — ",GetLastError());
     }
//--- 1つ前のローソク足の詳細を取得する
   if(copied<aver_period)
      return(false);
//---
   res.open=rt[aver_period].open;
   res.high=rt[aver_period].high;
   res.low=rt[aver_period].low;
   res.close=rt[aver_period].close;
//--- トレンド方向を特定する
   for(int i=0;i<aver_period;i++)
      aver+=rt[i].close;

   aver/=aver_period;

   if(aver<res.close)
      res.trend=UPPER;
   if(aver>res.close)
      res.trend=DOWN;
   if(aver==res.close)
      res.trend=FLAT;
//--- ローソク足が強気か弱気かを特定する
   res.bull=res.open<res.close;
//--- ローソク足実体サイズの絶対的なサイズを取得する
   res.bodysize=MathAbs(res.open-res.close);
//--- 髭のサイズを取得する
   double shade_low=res.close-res.low;
   double shade_high=res.high-res.open;
   if(res.bull)
     {
      shade_low=res.open-res.low;
      shade_high=res.high-res.close;
     }
   double HL=res.high-res.low;
//--- 以前のローソク足の平均実体サイズを計算する
   double sum=0;
   for(int i=1; i<=aver_period; i++)
      sum+=MathAbs(rt[i].open-rt[i].close);
   sum/=aver_period;

//--- ローソク足のタイプを特定する   
   res.type=CAND_NONE;
//--- 長い 
   if(res.bodysize>sum*m_long_coef && res.bull)
      res.type=CAND_LONG_BULL;
//--- 短い 
   if(res.bodysize<sum*m_short_coef && res.bull)
      res.type=CAND_SHORT_BULL;
//--- long bear
   if(res.bodysize>sum*m_long_coef && !res.bull)
      res.type=CAND_LONG_BEAR;
//--- sort bear
   if(res.bodysize<sum*m_short_coef && !res.bull)
      res.type=CAND_SHORT_BEAR;
//--- 同事
   if(res.bodysize<HL*m_doji_coef)
      res.type=CAND_DOJI;
//--- 丸坊主
   if((shade_low<res.bodysize*m_maribozu_coef && shade_high<res.bodysize*m_maribozu_coef) && res.bodysize>0)
      res.type=CAND_MARIBOZU;
//--- 唐笠
   if(shade_low>res.bodysize*m_hummer_coef2 && shade_high<res.bodysize*m_hummer_coef1)
      res.type=CAND_HAMMER;
//--- トンカチ
   if(shade_low<res.bodysize*m_hummer_coef1 && shade_high>res.bodysize*m_hummer_coef2)
      res.type=CAND_INVERT_HAMMER;
//--- コマ(極線)
   if((res.type==CAND_SHORT_BULL || res.type==CAND_SHORT_BEAR) && shade_low>res.bodysize*m_spin_coef && shade_high>res.bodysize*m_spin_coef)
      res.type=CAND_SPIN_TOP;
//---
   ArrayFree(rt);
   return(true);
  }

メソッドのアルゴリズムは次のとおりです。分析するパターンに応じて(既存のパターンまたは生成されたパターン)、選択範囲から初期日付を取得します。このメソッドは日付範囲内の計算ループで使用されるため、指定された時間枠の1つのローソク足だけ過去から未来に向かってシフトすることによって初期日付を変更します。次に、単純なローソク足タイプの計算に必要なデータをコピーします。このデータが十分でない場合は、適切なメッセージをユーザーに表示します。 

注意事項MetaTrader 5ターミナルで履歴データの可用性を監視する必要があります。そうしないと、アプリケーションが正しく動作しない可能性があります。 

データが十分であれば、現在のローソク足が単純なローソク足タイプに属するかどうかが確認されます。

以前の記事からわかるように、GetCategory()メソッドは履歴データを使用してパターン発生後の価格の行動を確認します。

//+------------------------------------------------------------------+
//| 利益カテゴリを特定する                                              |
//+------------------------------------------------------------------+
bool CProgram::GetCategory(const string symbol,const int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,int threshold)
  {
   MqlRates rt[];
   datetime start=TimeCurrent();
   if(m_table_number==1)
      start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   else if(m_table_number==2)
      start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
   start+=PeriodSeconds(timeframe)*shift;
   int copied=CopyRates(symbol,timeframe,start,4,rt);
//--- 1つ前のローソク足の詳細を取得する
   if(copied<4)
     {
      return(false);
     }
   double high1,high2,high3,low1,low2,low3,close0,point;
   close0=rt[0].close;
   high1=rt[1].high;
   high2=rt[2].high;
   high3=rt[3].high;
   low1=rt[1].low;
   low2=rt[2].low;
   low3=rt[3].low;
   if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point))
      return(false);

//--- 上昇トレンドかどうかを確認する
   if((int)((high1-close0)/point)>=threshold)
     {
      rate.a_uptrend++;
     }
   else if((int)((high2-close0)/point)>=threshold)
     {
      rate.b_uptrend++;
     }
   else if((int)((high3-close0)/point)>=threshold)
     {
      rate.c_uptrend++;
     }

//--- 下降トレンドかどうかを確認する
   if((int)((close0-low1)/point)>=threshold)
     {
      rate.a_dntrend++;
     }
   else if((int)((close0-low2)/point)>=threshold)
     {
      rate.b_dntrend++;
     }
   else if((int)((close0-low3)/point)>=threshold)
     {
      rate.c_dntrend++;
     }
   return(true);
  }

このメソッドのアルゴリズムでは、分析されたローソク足に関するデータを取得するためのメソッドのみが変更されています。これは新しい時間範囲選択ツールに直接関係しています。

両方のGetPatternType()に対する最後の共通メソッドは、結果表内のデータの取得、計算、および表示です。 

//+------------------------------------------------------------------+
//| Get, calculate and display data in the results table             |
//+------------------------------------------------------------------+
void CProgram::AddRow(CTable &table,string pattern_name,RATING_SET &rate,int found,ENUM_TIMEFRAMES timeframe)
  {
   int row=m_total_row;
   int total_patterns=ArraySize(m_total_combination);
   double p1,p2,k1,k2;
   int sum1=0,sum2=0;
   sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend;
   sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend;
//---
   p1=(found>0)?NormalizeDouble((double)sum1/found*100,2):0;
   p2=(found>0)?NormalizeDouble((double)sum2/found*100,2):0;
   k1=(found>0)?NormalizeDouble((m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found,3):0;
   k2=(found>0)?NormalizeDouble((m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found,3):0;

//---
   table.AddRow(row);
   if(m_table_number==1)
      table.SetValue(0,row,pattern_name);
   else if(m_table_number==2)
     {
      if(row<total_patterns)
         table.SetValue(0,row,m_total_combination[row]);
      else if(row>=total_patterns)
        {
         int i=row-int(total_patterns*MathFloor(double(row)/total_patterns));
         table.SetValue(0,row,m_total_combination[i]);
        }
     }
   table.SetValue(1,row,(string)found);
   table.SetValue(2,row,TimeframeToString(timeframe));
   table.SetValue(3,row,(string)p1,2);
   table.SetValue(4,row,(string)p2,2);
   table.SetValue(5,row,(string)k1,2);
   table.SetValue(6,row,(string)k2,2);
   ZeroMemory(rate);
   m_total_row++;
  }
//+------------------------------------------------------------------+

このメソッドは、引数として計算用のすべてのデータを受け取ります。データ処理アルゴリズムは非常に単純です。ここで2つの瞬間について言及するべきです。以前のアプリケーションバージョンでは、正確な行数が事前にわかっていました。例えば、既存のパターンデータを処理するとき、結果表には常にプリセットパターンの数、すなわち14行がありました。ユーザが任意の数のパターンまたは作業時間枠を選択できるため、行数がわからなくなりました。そのため、単純な行カウンタm_total_rowが追加されました。AddRow()メソッドを呼び出すと、patternとtimeframeの2つの記号に基づいて結果表に行が追加されます。

2番目の点は自動検索タブに関するものです。以前のバージョンでは、有限の行数は生成されたパターンの組み合わせの数と同じでした。同じ理由で、以前のアルゴリズムは現在は適していません。時間枠の数が不明であるからです。したがって、選択した各時間枠について、生成された組み合わせの配列全体をもう一度書き込む必要があります。

GetPatternType()メソッドの2番目のバリエーションを考えてみましょう。

bool              GetPatternType(const string symbol,string &total_combination[]);

ここでは、現在の銘柄に加えて、2番目のパラメータは生成されたパターンの文字列配列へのリンクです。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::GetPatternType(const string symbol,string &total_combination[])
  {
   CANDLE_STRUCTURE cand1[],cand2[],cand3[],cur_cand,prev_cand,prev_cand2;
   RATING_SET ratings;
   int total_patterns,m_pattern_total[];
   string elements[];
//---
   total_patterns=ArraySize(total_combination);
   ArrayResize(cand1,total_patterns);
   ArrayResize(cand2,total_patterns);
   ArrayResize(cand3,total_patterns);
   ArrayResize(m_pattern_total,total_patterns);
   ArrayResize(elements,m_pattern_size);
//---
   for(int i=0;i<total_patterns;i++)
     {
      StringReplace(total_combination[i],"[","");
      StringReplace(total_combination[i],"]","");
      if(m_pattern_size>1)
        {
         ushort sep=StringGetCharacter(",",0);
         StringSplit(total_combination[i],sep,elements);
        }
      m_pattern_total[i]=0;
      if(m_pattern_size==1)
         IndexToPatternType(cand1[i],(int)total_combination[i]);
      else if(m_pattern_size==2)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
        }
      else if(m_pattern_size==3)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
         IndexToPatternType(cand3[i],(int)elements[2]);
        }
     }
//---
   GetTimeframes(m_timeframes1,m_cur_timeframes2);
   int total=ArraySize(m_cur_timeframes2);
   if(total<1)
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      return(false);
     }
   m_total_row=0;
   m_table_number=2;
//--- すべての行を削除する
   m_table2.DeleteAllRows();
//---
   datetime start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
   datetime end=StringToTime(TimeToString(m_calendar4.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit4.GetHours()+":"+(string)m_time_edit4.GetMinutes()+":00");
//---
   if(start>end || end>TimeCurrent())
     {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return(false);
     }
//---
   if(m_pattern_size==1)
     {
      ZeroMemory(cur_cand);
      //--- Calculation by timeframes
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Calculation by patterns
         for(int j=0;j<total_patterns;j++)
           {
            //--- Calculation by a date range         
            for(int k=0;k<copied;k++)
              {
               //--- 現在のローソク足タイプを取得する
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                 // current candlestick
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+3,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }
            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
   else if(m_pattern_size==2)
     {
      ZeroMemory(cur_cand);
      ZeroMemory(prev_cand);
      //--- Calculation by timeframes
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Calculation by patterns
         for(int j=0;j<total_patterns;j++)
           {
            //--- Calculation by a date range         
            for(int k=0;k<copied;k++)
              {
               //--- 現在のローソク足タイプを取得する
               GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1);               // previous candlestick
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                  // current candlestick
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
                  prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+4,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }
            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
   else if(m_pattern_size==3)
     {
      ZeroMemory(cur_cand);
      ZeroMemory(prev_cand);
      ZeroMemory(prev_cand2);
      //--- Calculation by timeframes
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Calculation by patterns
         for(int j=0;j<total_patterns;j++)
           {
            //--- Calculation by a date range         
            for(int k=0;k<copied;k++)
              {
               //--- 現在のローソク足タイプを取得する
               GetCandleType(symbol,prev_cand2,m_cur_timeframes2[i],k+2);                                  // previous candlestick
               GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1);                                   // previous candlestick
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                                      // current candlestick
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
                  prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull && 
                  prev_cand2.type==cand3[j].type && prev_cand2.bull==cand3[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+5,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }

            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
//---
   m_table2.DeleteRow(m_total_row);
//--- 表を更新する
   m_table2.Update(true);
   m_table2.GetScrollVPointer().Update(true);
   return(true);
  }

入力データに基づいてこのバージョンアルゴリズム内の計算の順序を理解することが重要です。前述されているため、ここでは時間枠と日付範囲の受信は考察しません。その後、アルゴリズムは現在テストされているパターンのサイズを確認します。3本のローソク足から構成されるパターンについて考えてみましょう。価格データを格納するための構造体を宣言し、使用された「評価」構造体を無効にした後、アルゴリズムは最初に時間枠を反復処理し、それぞれについてコピーされたデータの量を取得します。これにより、特定パターンをさらに検索する範囲を決定することができます。時間枠を反復処理した後で、指定された時間枠の各パターンの計算を反復処理します。次に、各パターンについて、指定された日付範囲で定義されたローソク足を反復処理します。

よりよく理解するために、計算例と結果表に表示される情報の順序を確認してください。

図9 表に出力される計算例と結果の順序

図9からわかるように、テストはM15、M30、H1、H2の各時間枠で、EURUSD通貨ペア(1本のローソク足から構成されるパターン)を使用して実行されました。テスト用に2本(インデックス1と2)の単純なローソク足が選択されました。上記のアルゴリズムの実装は、結果表で確認できます。これは次のように実行されます。15分時間枠に続いて30分時間枠で、生成されたすべてのパターンが1つずつ分析されます。

終わりに

以下の添付ファイルには、説明されたすべてのファイルがフォルダに分類されています。正しく動作させるためには、MQL5フォルダをターミナルフォルダに保存するだけです。MQL5フォルダのあるルートフォルダを開くには、MetaTrader 5ターミナルで Ctrl+Shift+Dキーの組み合わせを押すか、下記の図10にあるようにコンテキストメニューを使用します。

図10 MetaTrader 5ターミナルルートでMQL5フォルダを開く

記事で使用されているプログラム

#
 名称
種類
説明
1
PatternAnalyzer.mq5 グラフィカルインターフェイス
 ローソク足パターンを分析するためのツールバー
2 MainWindow.mqh コードベース  GUIライブラリ
3 Program.mqh コードベース  UI作成と計算要素メソッドのライブラリ

これまでの連載記事:

ローソク足分析技術の研究(第1部): 既存パターンの確認
ローソク足分析技術の研究(第2部): 新規パターンの自動検索
ローソク足分析技術の研究(第3部): パターン操作のライブラリ