取引シグナルの多通貨監視(その3):検索アルゴリズムの紹介

Alexander Fedosov | 6 7月, 2020

目次

イントロダクション

前回の記事では、取引シグナルを監視するためのアプリの構成を作成しました。 また、基本的なインタラクション機能を備えたアプリケーションインターフェイスを実装しました。 さて、シンボルの設定と検索のアルゴリズムの視覚的な部分を埋める時が来ました。 前回の記事にあったプロジェクトをベースに、少しずつ新機能を追加していく予定です。

シンボルセット保存システム

前回は、最初のアプリケーション設定ステップで、マーケットウォッチからシンボルを選択するためのツールを作成しました。 シンボルは3つの方法で選択することができました。


図1 アプリケーション設定ステップ1とセーブドセットの設定

最初の2つの方法は非常にシンプルで、先に作成されています。 第三の方法を模索する必要があります。 では、これから何をしようとしているのか、より具体的に定義してみましょう。 図1の赤枠内の要素の相互作用をさらに深く掘り下げていくと、次のようなことが暗示されます。

プロジェクトを開き、CProgramの基底クラスを見つけ、そのプライベートセクションに2つのメソッドを追加します。 このメソッドは、シンボルテンプレートの読み込みと保存を担当します。

   bool              SaveSymbolSet(string file_name);
   bool              LoadSymbolSet(string file_name);

以下、これらのメソッドがどのように実装されているかを説明します。 

//+------------------------------------------------------------------+
//| Save template to a file                                          |
//+------------------------------------------------------------------+
bool CProgram::SaveSymbolSet(string file_name)
{
   if(file_name=="")
   {
      MessageBox("Select the template name to record","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Failed to create a configuration file","Signal Monitor");
      return(false);
   }
   else
      MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor");
//--- Save the selection of timeframes and patterns
   for(int i=0; i<m_all_symbols; i++)
      m_save.tf[i]=m_checkbox[i].IsPressed();
//---
   FileWriteStruct(h,m_save);
   FileClose(h);
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Load data to a panel                                             |
//+------------------------------------------------------------------+
bool CProgram::LoadSymbolSet(string file_name)
{
   if(file_name=="")
   {
      MessageBox("Select the template name to load","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration "+file_name+" not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_save);
   FileReadStruct(h,m_save);
//--- Loading timeframes
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].IsPressed(m_save.tf[i]);
      m_checkbox[i].Update(true);
   }
//---
   FileClose(h);
//---
   return(true);
}

しかし、今プロジェクトをコンパイルしようとすると、m_save変数に関連したエラーが発生します。 この構造体には、tf という名前の bool 型のパラメータが 1 つあります。 ユーザーが選択したファイルを記憶します。 そこで、アプリケーションクラスにこの構造体を作成し、そのインスタンスを基底クラスに追加します。

//+------------------------------------------------------------------+
//| Class for creating the application                               |
//+------------------------------------------------------------------+
struct SAVE
{
   bool     tf[100];
};
class CProgram : public CWndEvents
{
...
        SAVE            m_save;

OnEvent() に移動し、ボタンクリックイベントに関連する部分を入力し、ステップ1の条件に以下のコードを追加します。

         //--- Save the template
         if(lparam==m_save_button.Id())
         {
            SaveSymbolSet(m_text_edit.GetValue());
         }
         //--- Load the template
         if(lparam==m_load_button.Id())
         {
            LoadSymbolSet(m_text_edit.GetValue());
         }

また、上記ボタンのホットキーの使い方を実装してください。 同じメソッドで、キープレスイベントのチェックを追加し、使用するキーのコードを追加します。

//--- Key press
   if(id==CHARTEVENT_KEYDOWN)
   {
      if(m_current_step==1)
      {
         short sym=TranslateKey((int)lparam);
         //--- if the entered character is successfully converted to Unicode
         if(sym>0)
         {
            if(ShortToString(sym)=="l" || ShortToString(sym)=="д")
               LoadSymbolSet(m_text_edit.GetValue());
            if(ShortToString(sym)=="s" || ShortToString(sym)=="ы")
               SaveSymbolSet(m_text_edit.GetValue());
         }
      }
   }

プロジェクトをコンパイルします。 コンパイルに成功すると、以下のような結果が得られます。

図2 ユーザーテンプレートの保存と読み込み

トレーディングシグナルの追加と編集

さて、取引シグナルの作成と編集だけでなく、モニターでのさらなる追跡のためのメインアプリケーションの部分に移ります。 シグナルの作成・編集はこのような具合です。 

図3 シグナル作成・編集画面

現段階では、ウィンドウは様々なパラメータを制御するGUI要素のセットとして表示されます。 しかし、これらの設定はどこでも使われるものではありません。 インターフェイスに2つのボタンを追加することから始めます。 取引シグナルを追加/保存します。 別のボタンは、作成/編集をキャンセルします。 Program.mqh を開き、この2つのボタンの実装メソッドを基底クラスに追加します。

bool              CreateButton3(CButton &button,string text,const int x_gap,const int y_gap);

2つのCButtonボタンインスタンス。

   CButton           m_new_signal;
   CButton           m_cancel_button;

SetWindow.mqhに移動して、このメソッドを実装します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton3(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Save the window pointer
   button.MainPointer(m_set_window);
//--- Set up properties before creation
   button.XSize(60);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create the control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(1,button);
   return(true);
}

これら2つのボタンをシグナルを追加する取引ウィンドウのインターフェースに表示させるには、CreateSetWindow()メソッドのボディの最後に以下の行を挿入します。

//--- Add/Cancel Buttons
   if(!CreateButton3(m_new_signal,"Add",m_set_window.XSize()-2*(60+10),m_set_window.YSize()-(30+10)))
      return(false);
   if(!CreateButton3(m_cancel_button,"Cancel",m_set_window.XSize()-(60+10),m_set_window.YSize()-(30+10)))
      return(false);

プロジェクトのコンパイル後、トレーディングシグナル作成ウィンドウの下部に2つのボタンが表示されます。 

図4 シグナル作成ボタンとキャンセルボタンの追加

次に、ボタンをクリックしたときに発生するイベントを追加する必要があります。 キャンセルボタンの効果は明らかです:それは与えられたウィンドウ内のアクションや設定を保存せず、シグナルを追加せずにウィンドウを閉じます。 追加ボタンについて詳しく考えてみましょう。

まず、「追加」がクリックされたときに実行されるアクションの順序を決めてみましょう。

  1. ボタンをクリックすると、取引シグナル作成ウィンドウのUI要素を使用して選択したパラメータが保存されます。
  2. 保存に成功すると、ウィンドウが閉じられ、メインウィンドウのシグナルリストにシグナル名の最初のレコードが表示されます。
  3. レコードをクリックすると、以前に保存されたセットがシグナル設定UI要素に適用され、追加ボタンが保存に変換されます。

設定をファイルに保存できるようにするには、編集画面での視覚的な表示や、その後のシグナル検索に使用されるユニバーサルな設定を作成する必要があります。 そこで、構造体を作ってSIGNALと名づけましょう。 作成・編集画面での設定の設定は、この構造に書き込まれます。

struct SIGNAL
{
   int      ind_type;
   int      ind_period;
   int      app_price;
   int      rule_type;
   double   rule_value;
   int      label_type;
   uchar    label_value[10];
   color    label_color;
   color    back_color;
   color    border_color;
   bool     tooltip;
   uchar    tooltip_text[100];
   bool     image;
   int      img_index;
   bool     timeframes[21];
   TFNAME   tf_name[21];
};

構造体の各要素を見てみましょう。

ここで、作成したシグナルの設定を保存するための構造体の配列を基底クラスに宣言します。

SIGNAL            m_signal_set[5];

また、CProgramクラスのプライベート領域に、パラメータのセットをファイルに保存するためのメソッドと、ファイルから構造体にロードするためのメソッドを2つ作成します。

   bool              SaveSignalSet(int index);
   bool              LoadSignalSet(int index);

こちらが実装です。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveSignalSet(int index)
{
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Failed to create a configuration file","Signal Monitor");
      return(false);
   }
//--- Save the selection
   //--- Indicator type
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
   //--- Indicator period
   m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   //--- Type of applied price
   m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex();
   //--- Rule type
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
   //--- Rule value
   m_signal_set[index].rule_value=(double)m_rule_value.GetValue();
   //--- Text label display type
   m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1;
   //--- Save the value of the text field for the second type
   if(m_label_button[1].IsPressed())
      StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value);
   //--- Color of the text label
   m_signal_set[index].label_color=m_color_button[0].CurrentColor();
   //--- Background color
   if(m_set_param[0].IsPressed())
      m_signal_set[index].back_color=m_color_button[1].CurrentColor();
   else
      m_signal_set[index].back_color=clrNONE;
   //--- Border color
   if(m_set_param[1].IsPressed())
      m_signal_set[index].border_color=m_color_button[2].CurrentColor();
   else
      m_signal_set[index].border_color=clrNONE;
   //--- Tooltip value
   m_signal_set[index].tooltip=m_set_param[2].IsPressed();
   if(m_signal_set[index].tooltip)
      StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
   //--- Selected image
   m_signal_set[index].image=m_set_param[3].IsPressed();
   if(m_signal_set[index].image)
      m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
   //--- Selected timegrames
   int tf=0;
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
      {
         m_signal_set[index].timeframes[i]=true;
         StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
         tf++;
      }
      else
         m_signal_set[index].timeframes[i]=false;
   }
   //---
   if(tf<1)
   {
      MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print("Configuration signal_"+string(index)+" has been successfully saved");
//---
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_signal_set[index]);
   FileReadStruct(h,m_signal_set[index]);
//--- Loading indicator type
   m_indicator_type.SelectItem(m_signal_set[index].ind_type);
   RebuildParameters(m_signal_set[index].ind_type);
   m_indicator_type.GetButtonPointer().Update(true);
//--- Loading indicator period
   m_period_edit.SetValue((string)m_signal_set[index].ind_period);
   m_period_edit.GetTextBoxPointer().Update(true);
//--- Loading applied price
   if(!m_applied_price.IsLocked())
   {
      m_applied_price.SelectItem(m_signal_set[index].app_price);
      m_applied_price.GetButtonPointer().Update(true);
   }
//--- Loading signal rule
   m_rule_type.SelectItem(m_signal_set[index].rule_type);
   m_rule_type.GetButtonPointer().Update(true);
   m_rule_value.SetValue((string)m_signal_set[index].rule_value);
   m_rule_value.GetTextBoxPointer().Update(true);
//--- Loading a text label
   if(m_signal_set[index].label_type==0)
   {
      m_label_button[0].IsPressed(true);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(false);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(true);
   }
   else
   {
      m_label_button[0].IsPressed(false);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(false);
      m_text_box.ClearTextBox();
      m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value));
      m_text_box.Update(true);
   }
//--- Loading the color of the text label
   m_color_button[0].CurrentColor(m_signal_set[index].label_color);
   m_color_button[0].Update(true);
//--- Loading the background color
   if(m_signal_set[index].back_color==clrNONE)
   {
      m_set_param[0].IsPressed(false);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(true);
      m_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[0].IsPressed(true);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(false);
      m_color_button[1].CurrentColor(m_signal_set[index].back_color);
      m_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_signal_set[index].border_color==clrNONE)
   {
      m_set_param[1].IsPressed(false);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(true);
      m_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[1].IsPressed(true);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(false);
      m_color_button[2].CurrentColor(m_signal_set[index].border_color);
      m_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_signal_set[index].tooltip)
   {
      m_set_param[2].IsPressed(false);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(true);
      m_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(false);
      m_tooltip_text.ClearTextBox();
      m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text));
      m_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_signal_set[index].image)
   {
      m_set_param[3].IsPressed(false);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(true);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_set_param[3].IsPressed(true);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(false);
      m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//--- Loading selected timeframes
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked())
      {
         m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]);
         m_tf_button[i].Update(true);
      }
   }
//---
   FileClose(h);
   return(true);
}

このようにして、セーブ/ロードアルゴリズムによる第1の動作が完了します。 さて、作成されたシグナルのレコードとして機能するオブジェクトを作成する必要があります。 これらのオブジェクトをクリックすることにより、我々は以前の]作成された取引シグナルのパラメータを編集することができるようになります。 これらのオブジェクトを実装するために、CButtonクラスインスタンスの配列を作成します。

CButton           m_signal_editor[5];

また、オブジェクトを作成するメソッドを追加します。

bool              CreateSignalEditor(CButton &button,string text,const int x_gap,const int y_gap);

これらのオブジェクトはメインウィンドウに属しているので、このメソッドをStepWindow.mqhファイルに実装してください。

//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"

bool CProgram::CreateSignalEditor(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set up properties before creation
   button.XSize(110);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.IconXGap(3);
   button.IconYGap(7);
   button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp");
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create the control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

このメソッドを使用して、CreateStepWindow()ボディに5つのオブジェクトを追加し、これがシグナルのリストのオブジェクトとなります。

//---
   for(int i=0; i<5; i++)
   {
      if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+string(i),10,40*i+90))
         return(false);
   }

アプリケーションの起動後にこれらの要素の表示を無効にするには、 CreateGUI() メソッドで非表示にします。

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//---
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow("Color Picker"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_label_button[1].IsPressed(true);
   m_label_button[1].Update(true);
   for(int i=0; i<5; i++)
      m_signal_editor[i].Hide();
   return(true);
}

プロジェクトのコンパイルの前に行うべき次のことは、初期設定中に以前に保存したデータをすべて削除するメソッドを作成することです。 そのためには、ClearSaves()メソッドを作成し、CProgramクラスのコンストラクタで呼び出します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::ClearSaves(void)
{
   for(int i=0; i<5; i++)
      FileDelete("Signal Monitor\\signal_"+string(i)+".bin");
   m_total_signals=0;
   return(true);
}

さて、Add Signalボタンクリックイベントに以下を追加します。

//--- Add Signal button click event
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
         m_number_signal=-1;
         RebuildTimeframes();
         m_new_signal.LabelText("Add");
         m_new_signal.Update(true);
      }

プロジェクトのコンパイル後、新しい取引シグナルを追加する仕組みが完成しました。 次のステップは、以前に作成されたシグナルの編集の実装です。

図.5 新規取引シグナルの追加

全てをまとめてみましょう。 図5に示すように、Add Signalボタンをクリックすることでシグナルを追加することができるように実装しました。 また、シグナルリストに新しいシグナル名のボタンが追加されます。 現在は、編集することができないプリセット値となっています。 しかし、Signal_0をクリックしても何も起こらないので、これを修正しましょう。 選択したシグナルに対して以前に保存されていた設定をそのままインターフェイスにロードして、設定ウィンドウを再度開くことができるようにしてみましょう。 もう一つのアイデアは、ロードされた設定を編集して保存する可能性を実装することです。

CProgram基底クラスのOnEvent()メソッド本体を開き、ボタンクリックイベントのある部分を探します。 そこに以下のコードを追加します。

//---
      for(int i=0; i<5; i++)
      {
         if(lparam==m_signal_editor[i].Id())
         {
            LoadSignalSet(i);
            m_new_signal.LabelText("Save");
            m_new_signal.Update(true);
            m_set_window.OpenWindow();
            m_number_signal=i;
         }
      }

ここでは、作成されたシグナルボタンが押されたかどうかを判定します。 これを知った上で、LoadSignalSet()メソッドを使用して、以前に保存したデータを設定ウィンドウのインターフェースにロードし、ボタン名をAddからSaveに変更して、設定ウィンドウを開きます。


取引シグナルを作成・編集するためのツールの準備ができたので、シグナルの検索・表示を担当するアプリケーション部分にリンクさせてみましょう。 すでにシグナル監視の基礎はできています。 これには、行(最初の設定ステップで選択されたシンボル)と列(2番目の設定ステップで選択された時間枠)の名前を持つテーブル形式の外観があります。

図.6 取引シグナルモニター作成ボタン

少なくとも1つの取引シグナルが作成された後のアクションのシーケンスは単純です。 Createをクリックすると、それまでに設定されていた設定の配列全体に基づいて、取引シグナルモニターが形成されます。 このシステムのプログラミングを進める前に、Createを押した後に呼び出されるToMonitor()メソッドを補足する必要があります。

//--- Hide Step 3
   m_add_signal.Hide();

   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
   for(int i=0; i<5; i++)
      m_signal_editor[i].Hide();

現在作成されている取引シグナルの表示と編集を可能にするボタンオブジェクトを持っているので、これらのボタンは、前のステップ3からのすべてのコントロールと同様に、モニターウィンドウにジャンプするときにも非表示にする必要があります。

第1回目の記事では、アプリケーションの構造を開発した際に、モニター要素の一つとして、第1回目の記事の図5に示すような表示ブロックがありました。 その目的は、以前に作成された取引シグナルの一つの存在をリアルタイムで表示することです。 したがって、まず、指示ブロックとして使用されるオブジェクトを作成します。 これは、CProgramクラスのCreateSignalButton()メソッドを実装することで行うことができます。 

bool              CreateSignalButton(CButton &button,const int x_gap,const int y_gap);

また、表示ブロックのフルセットを作成するために必要なCButtonクラスインスタンスの配列を追加します。

CButton           m_signal_button[];

今度はStepWindow.mqhを開いて、作成したメソッドの実装をファイルの最後に追加します。

//+------------------------------------------------------------------+
//| Creates an indication block                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalButton(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'220,225,235';
//--- Save the window pointer
   button.MainPointer(m_step_window);
//--- Set up properties before creation
   button.TwoState(false);
   button.XSize(40);
   button.YSize(20);
   button.IconXGap(2);
   button.IconYGap(button.YSize()/2-8);
   button.LabelXGap(19);
   button.LabelYGap(2);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrBlack);
   button.LabelColorPressed(clrSlateGray);
   button.IconFile("");
   button.IconFileLocked("");
   button.IsDoubleBorder(true);
//--- Create the control
   if(!button.CreateButton("",x_gap-button.XSize()/2,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

今度はToMonitor()の作成方法で適用します。 これを行うには、以下のように、メソッド本体のTimeframesセクションを見つけて、メソッドにコードを追加します

//--- Timeframes
   int tf=ArraySize(m_timeframes);
   ArrayResize(m_timeframe_label,tf);
//---
   for(int i=0; i<tf; i++)
   {
      if(!CreateTimeframeLabel(m_timeframe_label[i],110+50*i,m_step_window.CaptionHeight()+3,m_timeframes[i]))
         return;
      m_timeframe_label[i].Update(true);
   }
//-- Signal blocks
   int k=0;
   ArrayResize(m_signal_button,sy*tf);
   for(int j=0; j<sy; j++)
   {
      for(int i=0; i<tf; i++)
      {
         if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/2,m_step_window.CaptionHeight()+25+j*25))
            return;
         m_signal_button[k].Update(true);
         k++;
      }
   }
//--- Resize window
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+5,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+5);

プロジェクトをコンパイルして、将来の取引シグナルの表示のための既製のレイアウトを取得します。


図.7 取引シグナルの用意されたレイアウト

さて、表示ブロックのどの要素が特定のシグナルを表示するように構成することができるかを心にとめておいてください。

これらのプロパティを管理するために、CProgram基底クラスのプライベートセクションに以下のメソッドを設定します。

   void              SetBorderColor(int index, color clr);
   void              SetLabel(int index, string text,color clr=clrBlack);
   void              SetIcon(int index,int number);
   void              SetBackground(int index,color clr);
   void              SetTooltip(int index,string text="\n");

実装:

//+------------------------------------------------------------------+
//| Set the border color                                             |
//+------------------------------------------------------------------+
void CProgram::SetBorderColor(int index, color clr)
{
   m_signal_button[index].BorderColor(clr);
   m_signal_button[index].BorderColorHover(clr);
   m_signal_button[index].BorderColorPressed(clr);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the label text                                               |
//+------------------------------------------------------------------+
void CProgram::SetLabel(int index, string text,color clr=clrBlack)
{
   m_signal_button[index].LabelColor(clr);
   m_signal_button[index].LabelColorHover(clr);
   m_signal_button[index].LabelColorPressed(clr);
   m_signal_button[index].LabelText(text);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the background                                               |
//+------------------------------------------------------------------+
void CProgram::SetBackground(int index,color clr)
{
   m_signal_button[index].BackColor(clr);
   m_signal_button[index].BackColorHover(clr);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the icon                                                     |
//+------------------------------------------------------------------+
void CProgram::SetIcon(int index,int number)
{
   //---
   string image[]=
   {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp"
   };
   string path=(number>=0)?image[number]:"";
   if(number<0)
      m_signal_button[index].IsCenterText(true);
   else
      m_signal_button[index].IsCenterText(false);
   m_signal_button[index].IconFile(path);
   m_signal_button[index].IconFilePressed(path);
   m_signal_button[index].Update(true);
}
//+------------------------------------------------------------------+
//| Set the tooltip                                                  |
//+------------------------------------------------------------------+
void CProgram::SetTooltip(int index,string text="\n")
{
   m_signal_button[index].Tooltip(text);
   m_signal_button[index].ShowTooltip(true);
}

次に、計算、正しい表示、そして最も重要なことに、作成された指示ブロックのそれぞれを特定の行(選択されたシンボル)と列(時間枠)に対応させるために必要ないくつかの補助的なメソッドを作成する必要があります。 まず、テーブル内のインデックスに基づいて、指示ブロックの行と列を決定するための方法を作成します。

   int               GetRow(int index,int row_size);
   int               GetCol(int index,int row_size);
//+------------------------------------------------------------------+
//| Determining a row by the index                                   |
//+------------------------------------------------------------------+
int CProgram::GetRow(int index,int row_size)
{
   return(int(MathFloor(index/row_size)+1));
}
//+------------------------------------------------------------------+
//| Determining a column by the index                                |
//+------------------------------------------------------------------+
int CProgram::GetCol(int index,int row_size)
{
   return(int(MathMod(index,row_size)+1));
}

また、インターフェースから必要なデータを取得できるようにする必要があります。 つまり、タイムフレームのテキスト表示をタイムフレーム列挙型に変換する必要があります。 また、表示ブロック・インデックスに基づいて、それが表のどのシンボルと時間枠に対応しているかを見つけることができる必要があります。

//+------------------------------------------------------------------+
//| Return timeframe by row                                          |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CProgram::StringToTimeframe(const string timeframe)
{
   if(timeframe=="M1")  return(PERIOD_M1);
   if(timeframe=="M2")  return(PERIOD_M2);
   if(timeframe=="M3")  return(PERIOD_M3);
   if(timeframe=="M4")  return(PERIOD_M4);
   if(timeframe=="M5")  return(PERIOD_M5);
   if(timeframe=="M6")  return(PERIOD_M6);
   if(timeframe=="M10") return(PERIOD_M10);
   if(timeframe=="M12") return(PERIOD_M12);
   if(timeframe=="M15") return(PERIOD_M15);
   if(timeframe=="M20") return(PERIOD_M20);
   if(timeframe=="M30") return(PERIOD_M30);
   if(timeframe=="H1")  return(PERIOD_H1);
   if(timeframe=="H2")  return(PERIOD_H2);
   if(timeframe=="H3")  return(PERIOD_H3);
   if(timeframe=="H4")  return(PERIOD_H4);
   if(timeframe=="H6")  return(PERIOD_H6);
   if(timeframe=="H8")  return(PERIOD_H8);
   if(timeframe=="H12") return(PERIOD_H12);
   if(timeframe=="D1")  return(PERIOD_D1);
   if(timeframe=="W1")  return(PERIOD_W1);
   if(timeframe=="MN")  return(PERIOD_MN1);
//--- The default value
   return(::Period());
}
//+------------------------------------------------------------------+
//| Determine the timeframe                                          |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CProgram::GetTimeframe(int index)
{
   int tf=ArraySize(m_timeframes);
   return(StringToTimeframe((m_timeframe_label[GetCol(index,tf)-1].LabelText())));
}
//+------------------------------------------------------------------+
//| Determine the symbol                                             |
//+------------------------------------------------------------------+
string CProgram::GetSymbol(int index)
{
   int tf=ArraySize(m_timeframes);
   return(m_symbol_label[GetRow(index,tf)-1].LabelText());
}

次の方法は、シグナル検索アルゴリズムに直接関連しています:これは、指定されたシンボルと時間枠上で以前に作成されたシグナルのパラメータのセットを検索します。

bool              GetSignal(string sy,ENUM_TIMEFRAMES tf,SIGNAL &signal_set);

設定は、パラメータの集合からSIGNAL構造体で渡されます。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::GetSignal(string sy,ENUM_TIMEFRAMES tf,SIGNAL &signal_set)
{
//--- Getting the indicator handle
   int h=INVALID_HANDLE;
   ENUM_APPLIED_PRICE app_price;
   switch(signal_set.app_price)
   {
   case  0:
      app_price=PRICE_CLOSE;
      break;
   case  1:
      app_price=PRICE_OPEN;
      break;
   case  2:
      app_price=PRICE_HIGH;
      break;
   case  3:
      app_price=PRICE_LOW;
      break;
   case  4:
      app_price=PRICE_MEDIAN;
      break;
   case  5:
      app_price=PRICE_TYPICAL;
      break;
   case  6:
      app_price=PRICE_WEIGHTED;
      break;
   default:
      app_price=PRICE_CLOSE;
      break;
   }
//---
   switch(signal_set.ind_type)
   {
   case  0:
      h=iATR(sy,tf,signal_set.ind_period);
      break;
   case  1:
      h=iCCI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  2:
      h=iDeMarker(sy,tf,signal_set.ind_period);
      break;
   case  3:
      h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK);
      break;
   case  4:
      h=iWPR(sy,tf,signal_set.ind_period);
      break;
   case  5:
      h=iRSI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  6:
      h=iMomentum(sy,tf,signal_set.ind_period,app_price);
      break;
   default:
      break;
   }
   if(h==INVALID_HANDLE)
   {
      Print(sy+". Failed to get handle");
      Print("Handle = ",h,"  error = ",GetLastError());
      return(false);
   }
   //---
   double arr[1];
   if(CopyBuffer(h,0,
    0,1,arr)!=1)
   {
      Print("sy= ",sy,"tf= ",EnumToString(tf)," Failed to get handle data ",GetLastError());
      return(false);
   }
   IndicatorRelease(h);
//--- Check the condition
   double r_value=signal_set.rule_value;
   double c_value=arr[0];
   m_ind_value=c_value;
   int s=0;
   switch(signal_set.rule_type)
   {
   case  0:
      if(c_value>r_value)
         s=1;
      break;
   case  1:
      if(c_value>=r_value)
         s=1;
      break;
   case  2:
      if(c_value==r_value)
         s=1;
      break;
   case  3:
      if(c_value<r_value)
         s=1;
      break;
   case  4:
      if(c_value<=r_value)
         s=1;
      break;
   default:
      s=0;
      break;
   }
//---
   if(s>0)
      return(true);
   return(false);
}

GetSignal()メソッドは、SIGNAL構造体から、利用可能なインジケーターのうち、どのインジケーターが取引シグナルを生成するために選択されたか、どの設定がインジケーターに選択されたか、どの検索ルールが設定されたかの情報を受け取ります。 時間枠によるフィルタリングは、各シグナルに対して2回実行できることを忘れないでください。 最初は第2のセットアップステップで実行され、その後、下の図8に示すように、取引シグナル作成ウィンドウで選択された値をフィルタリングすることができます。

図.8 作成したシグナルのタイムフレームの選択

私たちのアルゴリズムがこのフィルターを考慮し、与えられた時間枠外のシグナルを探さないようにするためには、作成された取引シグナルのそれぞれについて時間枠の指定をチェックする必要があります。 そこで、基底クラスにCheckTimeframe()メソッドを作成します。 メソッドがフィルターとして機能します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckTimeframe(ENUM_TIMEFRAMES tf,SIGNAL &signal_set)
{
   for(int i=0; i<21; i++)
   {
      if(StringToTimeframe(CharArrayToString(signal_set.tf_name[i].tf))==tf)
         return(true);
   }
   return(false);
}

さて、いよいよ売買シグナル検索の仕組みを作る時が来ました。 これを行うには、CProgramクラスのpublicセクションにメソッドを追加します。SearchSignal() のセクションにメソッドを追加します。

bool              SearchSignals(void);

ステップバイステップの実装をより詳細に分析し、先に作成された補助メソッドの目的を見てみましょう。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SearchSignals(void)
{
//--- Search for set signals
   SIGNAL signal_set[];
   int cnt=0;
   for(int i=0; i<5; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt++;
   }
//---
   ArrayResize(signal_set,cnt);
   ZeroMemory(signal_set);
//---
   for(int i=0; i<cnt; i++)
   {
      int h=FileOpen("Signal Monitor\\signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      FileReadStruct(h,signal_set[i]);
      FileClose(h);
      for(int j=0; j<ArraySize(m_signal_button); j++)
      {
         //---
         string sy=GetSymbol(j);
         ENUM_TIMEFRAMES tf=GetTimeframe(j);
         //---
         if(!CheckTimeframe(tf,signal_set[i]))
            continue;
         //---
         if(GetSignal(sy,tf,signal_set[i]))
         {
            //---
            if(signal_set[i].label_type==1)
               SetLabel(j,CharArrayToString(signal_set[i].label_value),signal_set[i].label_color);
            else
               SetLabel(j,DoubleToString(m_ind_value,3),signal_set[i].label_color);
            //---
            if(signal_set[i].back_color!=clrNONE)
               SetBackground(j,signal_set[i].back_color);
            //---
            if(signal_set[i].border_color!=clrNONE)
               SetBorderColor(j,signal_set[i].border_color);
            else
               SetBorderColor(j,signal_set[i].back_color);
            //---
            if(signal_set[i].tooltip)
               SetTooltip(j,CharArrayToString(signal_set[i].tooltip_text));
            //---
            if(signal_set[i].image)
               SetIcon(j,signal_set[i].img_index);
            else
               SetIcon(j,-1);
         }
      }
   }
   return(true);
}

最初の操作ステップでは、検索方法は、作成され、設定された取引シグナルの合計数に関するデータを収集します。 その後、このメソッドは、シグナル設定に関する情報を接続するファイルをループし、このデータを構造体に読み込みます。 この機構は、各指示ブロックに対して、表形式 でブロックが対応する適切なシンボルと時間枠を決定します。 このデータに基づいて、選択した時間枠で取引シグナルを検索する必要があるかどうかをチェックします。 タイムフレームが一致した場合は、シグナルを検索します。 シグナルが検出された場合は、シグナルの構成に応じて表示ブロックを色付けします。 これで、作成したメソッドを適用することができます。 このメソッドは、ToMonitor()メソッド本体の最後に呼び出されなければなりません。

...
//--- Resize window
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+5,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+5);
//---
   SearchSignals();
}

では、一定時間経過後に繰り返し検索を有効にしてみましょう。 SignalMonitor.mq5ファイルを開き、ファイルの先頭に列挙を作成します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,        // 1 Minute
   MINUTE_15,     // 15 Minutes
   MINUTE_30,     // 30 Minutes
   HOUR,          // 1 Hour
   HOUR_4         // 4 Hours
};

入力に簡単に設定を追加できるようになりました。

input UPDATE               Update            =  HOUR;                // Update interval

計算用の2つの9つの変数を作成します。

int cnts=0;
datetime update;

エキスパートの初期化で以下の行を追加します。

//---
   switch(Update)
   {
   case MINUTE:
      cnts=60;
      break;
   case MINUTE_15:
      cnts=60*15;
      break;
   case MINUTE_30:
      cnts=60*30;
      break;
   case HOUR:
      cnts=3600;
      break;
   case HOUR_4:
      cnts=3600*4;
      break;
   default:
      cnts=1;
      break;
   }
   update=TimeLocal()+cnts;

このように、更新間隔を決定し、次回の更新時刻を設定している。 OnTick()関数本体に、指定された時間間隔が経過した場合、再度取引シグナルを検索するタイムチェックを追加します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   if(TimeLocal()>update)
   {
      program.SearchSignals();
      update=TimeLocal()+cnts;
   }
}

プロジェクトをコンパイルして、独自のシンボルのセットを作成します。 モニター動作を実演するためのシグナルを1つ追加することができます。

 

このシリーズの次の部分では、現在の機能をより柔軟な取引シグナルの設定に向けて拡張し、既存の機能を改善していきます。


結論

以下に添付されているアーカイブには、説明されているすべてのファイルがフォルダに適切に配置されています。 正しく操作するためには、端末のルートディレクトリのMQL5フォルダに保存する必要があります。 MQL5フォルダがあるターミナル・ルート・ディレクトリを開くには、MetaTrader 5ターミナルでCtrl+Shift+Dキーの組み合わせを押すか、以下の図9に示すようにコンテキスト・メニューを使用します。


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