トレードシグナルの多通貨監視(その5: 複合シグナル

Alexander Fedosov | 26 8月, 2020

目次

イントロダクション

この記事のシリーズの前のパートでは、多通貨取引のシグナルモニターの作成をし、カスタムインジケータを使用して、データをフィルタリングすることができるようになり、論理的なルールのシステムを拡張する可能性を紹介しました。 しかし、多くのトレーダーや開発者は、複数の指標に基づいて取引システムを使用しています。 トレンド、レンジ、オシレータなどのインジケータタイプはそれぞれ、特定の相場では短所があります。

例えば、横方向の動きが長引いている間は、トレンドインジケータが多くのダマシシグナルを出すことがよくあります。 したがって、他のツールで確認する必要があります。 これは、メインインジケータによって生成されたシグナルのフィルタとして機能するメインインジケータに加えて、より多くのインジケータを追加することによって行うことができます。 オプションで、トレーダーは、同じ相場の状態を示すインジケータのセットに基づいてトレードシステムを作成することができます。 この場合、複数のインジケータを同時に確認することで、エントリーシグナルを生成することができます。

したがって、インジケータ開発における次の論理的なステップは、利用可能なシンプルなもので構成された複合シグナルの導入です。 このように、合成シグナルの概念を用いてタスクを行います。 前のパートのプロジェクトを元にします。 前の記事の最後にでプロジェクトを公開します。


コンポジットシグナル

まず、今回のプロジェクトで使用する「コンポジットシグナル」の概念を定義してみましょう。 以前のバージョンでは、受信したソースに特定の条件を適用して作成されたシンプルなシグナルを使用していましたが、RSI、WPR、CCIなどの様々なインジケータによって提示される可能性があります。 また、カスタムインジケータを使用する可能性も実装されました。

コンポジットシグナル は、論理的なAND/OR演算子によって互いに接続された2つ以上のシンプルなシグナルからなるシグナルです。

したがって、合成シグナルは、論理演算子を使用して相互に相互作用する、以前に作成されたシンプルなシグナルを含むことになります。 所定の時間内に2個または3個の同時シンプルシグナルが存在する条件を含む複素シグナルを作成することが可能となる。 このように、トレードシステムは1つのメインシグナルとフィルタを持つことになります。 論理和演算子を使用することで、複数の方向の売買シグナルを一度に検索することができ、現在の相場を分析する上でより広い範囲をカバーすることができます。

より良い説明に、ここでは、アプリケーションの更新と強化の計画をご紹介します。 まず、アプリケーションのインターフェイスを更新し、新しいコントロールを追加する必要があります。


図1. UI要素の追加

図1の左の部分は既にお馴染みですね。 既に追加されている簡単なシグナルに続いて、シグナルを追加するためのボタンです。 ここで、合成シグナルを追加するためのボタン(1)、追加された合成シグナルのリスト(2)、およびシンプルなシグナルに使用フラグを設定するための第3のグループのスイッチボタン(3)を作成する必要があります。

合成シグナルの生成スキームを図2に示します。 シンプル1とシンプル2と名付けられた要素は、複雑なものを作成する際に使用できる、既に作成されたシンプルなシグナルのリストを示します。 シグナルの使用属性としては、図2に示すシグナルは、「C」または「B」の属性を有している必要があり、そうでなければリストには表示されません。

図2 合成シグナル作成・編集画面の構成図

続いて、Ruleと呼ばれるセクションがあり、ここでは合成シグナルが作成されます。 上記のリストのうち、3つのロットのいずれかにシグナルフォームを追加することができます。 スロット間の論理演算子は、ボタンを使用して選択されます。 この合成シグナルのルールを設定します。 例えば、シンプル1とシンプル2を設定した場合、この2つのシンプルシグナルが同時に出現した場合にのみ、合成シグナルが表示されます。


関数の実装

アプリケーションに新たな追加関数を導入する前に、既存の要素を新関数で利用できるように準備しておく必要があります。 シンプルなシグナルのリストのUI要素を変更する必要があります。 図3に示すように、シンプルなシグナルのシステム名が記載されたボタンは、ユーザが指定したシグナル名が記載されたテキストラベルと、編集ボタンと、シグナル使用フラグが記載されたボタンに置き換えられます。


図3. シグナル編集制御の更新

さて、前回の記事でダウンロードしたプロジェクトを開き、追加タスクを進めます。 まず、CreateSignalEditor()メソッドと、編集ボタンの作成に使用するm_signal_editor[]配列を削除します。 新しい要素やメソッドの作成に進む前に、シンプルシグナルと合成シグナルの最大許容数を決定する2つのマクロ置換を紹介します。

#define SIMPLE_SIGNALS 10
#define COMPOSITE_SIGNALS 5

 次に、CreateStepWindow()メインウィンドウ作成メソッド本体のトレーリングストップに移動し、代わりに以下のコードを記述します。

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

新しいシグナルの表示と編集の新しい実装を追加します。

   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(!CreateLabel(m_signal_label[i],10,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_signal_ind[i],"Edit",C'255,195,50',150,40*i+90))
         return(false);
      if(!CreateSignalSet(m_signal_type[i],"S",C'75,190,240',150+35,40*i+90))
         return(false);
   }

実装には、CButtonクラスインスタンスの2つの新しい配列、m_signal_ind[]とm_signal_type[]と、CTextLabel m_signal_label[]配列があります。 プロジェクトの基底クラスCProgramに追加します。

   CTextLabel        m_signal_label[SIMPLE_SIGNALS];
   CButton           m_signal_ind[SIMPLE_SIGNALS];
   CButton           m_signal_type[SIMPLE_SIGNALS];

また、新しいCreateSignalSet()メソッドを宣言して実装します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalSet(CButton &button,string text,color baseclr,const int x_gap,const int y_gap)
{
//--- Store the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   button.XSize(30);
   button.YSize(30);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

このようにして、図3から更新されたインタラクションインターフェースを有するシンプルなシグナルのリストを形成しました。 複合シグナルのリストは、基底クラス に変数m_c_signal_label[]とm_c_signal_ind[]を追加し、以下のコードCreateStepWindow()メソッドに追加することで追加されます。

CTextLabel        m_c_signal_label[COMPOSITE_SIGNALS];
CButton           m_c_signal_ind[COMPOSITE_SIGNALS];
//---
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(!CreateLabel(m_c_signal_label[i],300,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_c_signal_ind[i],"Edit",C'255,195,50',150+290,40*i+90))
         return(false);
   }

簡易シグナル・合成シグナルの表示・編集ツールを新たに作成しました。 最初のアプリケーションセットアップのステップ3で、テキストヘッダと一緒にコンポジットシグナルを作成するためのボタンを追加します。 CButtonクラスのインスタンスでAddSignalボタンで使用する変数m_add_signalから静的配列m_add_signal[2]を作成することで行われます。 CreateStepWindow()メソッドのボディで以下のコードを置き換えてください。

   if(!CreateIconButton(m_add_signal,m_lang[15],10,30))
      return(false);

合成シグナル作成ボタンを含む新しいものに変更しました。

   if(!CreateIconButton(m_add_signal[0],m_lang[15],10,30))
      return(false);
   if(!CreateIconButton(m_add_signal[1],m_lang[43],300,30))
      return(false);

シンプルシグナルと複合シグナルのリストのヘッダを表示するには、m_signal_header 変数を m_signal_header[2] 静的配列にします。 前のコード。

   if(!CreateLabel(m_signal_header,10,30+30+10,m_lang[16]))
      return(false);

2つのヘッダを持つ新しいものに置き換える必要があります。

   if(!CreateLabel(m_signal_header[0],10,30+30+10,m_lang[16]))
      return(false);
   if(!CreateLabel(m_signal_header[1],300,30+30+10,m_lang[44]))
      return(false);

以上の変更を全て行った後、ステップ3で更新されたインターフェースは以下のようになります。

図4 トレードシグナル作成ボタンの追加と更新 

さて、シンプルなトレードシグナルのリストの以前に作成したオブジェクトを、作成・編集イベントにリンクさせる必要があります。 ここで予告を追加しておきます。 UI要素の数が増えているため、ユーザとのインタラクションの可能性も増えているため、OnEvent()ハンドラ本体が長くなりすぎて、どの要素に属するイベントなのか、どのイベント群に属するイベントなのかがわかりにくくなっています。 よって、UIとの主要なインタラクションはすべて適切なメソッドでラップすることにしました。 これより、2つの利点が得られます。OnEvent()はキーイベントのリストがあるので、それぞれのロジックとコードにアクセスできます。

新しいシンプルなトレードシグナルの追加を担当するコードの断片を見つけ、必要なコードを追加し、AddSimpleSignal()メソッドに移動します。

//+------------------------------------------------------------------+
//| Adds a new simple trading signal                                 |
//+------------------------------------------------------------------+
void CProgram::AddSimpleSignal(long lparam)
{
   if(lparam==m_new_signal.Id())
   {
      if(m_number_signal<0)
      {
         if(SaveSignalSet(m_total_signals))
         {
            m_set_window.CloseDialogBox();
            if(m_total_signals<SIMPLE_SIGNALS)
            {
               m_total_signals++;
               m_signal_label[m_total_signals-1].Show();
               m_signal_label[m_total_signals-1].LabelText(m_signal_name.GetValue());
               m_signal_label[m_total_signals-1].Update(true);
               m_signal_type[m_total_signals-1].Show();
               m_signal_ind[m_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
            //---
            if(m_total_signals>1)
            {
               m_add_signal[1].IsLocked(false);
               m_add_signal[1].Update(true);
            }
         }
      }
      else
      {
         if(SaveSignalSet(m_number_signal,false))
         {
            m_set_window.CloseDialogBox();
            m_signal_label[m_number_signal].LabelText(m_signal_name.GetValue());
            m_signal_label[m_number_signal].Update(true);
         }
      }
   }
}

OnEvent()ハンドラで呼び出す。 このように、メソッド本体のサイズを小さくして、その実装部分を構造化していきます。 図3は、新しい実装がカスタムシグナル名を設定する可能性をサポートすることを示します。 簡易シグナルの作成・編集ウィンドウに以下のフィールドを追加することで行うことができます。 CreateSignalName()という新しいメソッドを作成し、シグナル名のインプットフィールドを追加します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(110);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().XGap(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[44]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[44],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

また、シンプルなシグナル名のインプットフィールドをSIGNAL構造体に追加することも忘れないようにしてください。

uchar             signal_name[50];

メソッドSaveSignalSet()LoadSignalSet()も、既存の設定に加えてシグナル名を記録するように更新する必要があります。 新しいシグナルを作成する際には、新しいシグナル名が以前に作成されたシグナルの名前と一致してしまうような状況を避けるべきです。 避けるべきもう一つの状況は、以前に作成されたシグナルのファイルにパラメータが保存されている場合です。 したがって、SaveSignalSet()メソッドの第2引数first_saveを追加します。 シグナルが初めて保存されたか、または以前に作成したシグナルの編集済みパラメータが保存されたかを表示します。

bool              SaveSignalSet(int index,bool first_save=true);

メソッドの完全な実装は以下のようになります。

bool CProgram::SaveSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckSignalNames(m_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>SIMPLE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(SIMPLE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_signal_name.GetValue(),m_signal_set[index].signal_name);
//--- Indicator type
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
//--- Indicator period
   if(m_signal_set[index].ind_type!=9)
   {
      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();
   }
   else
   {
      string path=m_custom_path.GetValue();
      string param=m_custom_param.GetValue();
      if(path=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите путь к индикатору","Монитор сигналов");
         else
            MessageBox("Enter the indicator path","Signal Monitor");
         FileClose(h);
         return(false);
      }
      if(param=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите параметры индикатора через запятую","Монитор сигналов");
         else
            MessageBox("Enter indicator parameters separated by comMA","Signal Monitor");
         FileClose(h);
         return(false);
      }
      StringToCharArray(path,m_signal_set[index].custom_path);
      StringToCharArray(param,m_signal_set[index].custom_val);
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   }
//--- Rule type
   m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex();
//--- Comparison type
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
//--- Rule value
   m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue();
   m_signal_set[index].rule_value2=(double)m_rule_value[1].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();
//--- Backdrop 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;
//--- Hint 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)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не выбран ни один таймфрейм","Монитор сигналов");
      else
         MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print("Configuration "+m_signal_name.GetValue()+" successfully saved");
//---
   return(true);
}

上のコードでは、新しいシグナルの符号のチェックと、指定した名前のシグナルが既に存在するかどうかをチェックするCheckSignalNames()メソッドをハイライトします。 さらなるコードは、シグナル名があるかどうか、およびその長さが少なくとも 3 文字であるかどうかをチェックします。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckSignalNames(string name)
{
//--- Search for set signals
   SIGNAL signal_set[];
   int cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt++;
   }
   if(cnt<1)
      return(true);
   //---
   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]);
      if(CharArrayToString(m_signal_set[i].signal_name)==name)
      {
         FileClose(h);
         return(false);
      }
      FileClose(h);
   }
   return(true);
}

さて、下のスクリーンショットのように、新しいフォーマットで簡単なトレードシグナルを作成することができます。 作成されたシグナル編集ボタンを有効にして、シンプルなシグナルに使用シンボルがどのように割り当てられているかを見てみましょう。

図5 作成された簡易売買シグナルの新フォーマット 

シンプルなシグナルの編集を有効にするには、新しい EditSimpleSignal() メソッドを作成し、OnEvent() ハンドラで呼び出します。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditSimpleSignal(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_signal_ind[i].Id())
      {
         LoadSignalSet(i);
         m_new_signal.LabelText(m_lang[38]);
         m_new_signal.Update(true);
         m_set_window.OpenWindow();
         m_number_signal=i;
      }
   }
}

同様に、符号を切り替えるには、新たにSignalSwitch()メソッドを作成し、ハンドラ内で呼び出します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSwitch(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      //---
      if(lparam==m_signal_type[i].Id())
      {
         if(m_signal_type[i].LabelText()=="S")
            SetButtonParam(m_signal_type[i],"C",clrMediumOrchid);
         else if(m_signal_type[i].LabelText()=="C")
            SetButtonParam(m_signal_type[i],"B",C'240,120,0');
         else if(m_signal_type[i].LabelText()=="B")
            SetButtonParam(m_signal_type[i],"S",C'75,190,240');
      }
   }
}

スイッチは下図6のようになっています。 これは簡単なボタンクリックで行います。

図6 簡易シグナル利用符号の視覚的な切り替え

トレーディングシグナルは、複合トレーディングシグナルの論理的な要素として利用できるようになりました。

以前、新しいコンポジットシグナルを作成するボタンを追加しました - 'Add Composite Signal'です。 ここで、コンポジットシグナルの作成、設定、編集を可能にするダイアログウィンドウを実装する必要があります。 先ほど、簡単なシグナル編集ウィンドウを作成する際に、インクルードファイルSetWindow.mqh及び そこで、未来のコンポジット・シグナル・エディタのUIとなるCSetWindow.mqhを作成します。 新しく作成したファイルを開き、Program.mqhを追加して、CProgramの基底クラスにアクセスします。

//+------------------------------------------------------------------+
//|                                                   CSetWindow.mqh |
//|                                Copyright 2020, Alexander Fedosov |
//|                           https://www.mql5.com/ja/users/alex2356 |
//+------------------------------------------------------------------+
#include "Program.mqh"

Program.mqhファイルで、CSetWindow.mqhを既存のコントロールに接続します。

//+------------------------------------------------------------------+
//| Add Controls                                                     |
//+------------------------------------------------------------------+
#include "MainWindow.mqh"
#include "SetWindow.mqh"
#include "StepWindow.mqh"
#include "CSetWindow.mqh"

ダイアログ・ウィンドウを作成するために、CreateCompositeEdit()基底クラスに新しいメソッドを導入し、新しく作成したインクルード・ファイルに実装します。

protected:
   //--- Forms
   bool              CreateStepWindow(const string caption_text);
   bool              CreateSetWindow(const string caption_text);
   bool              CreateColorWindow(const string caption_text);
   bool              CreateFastEdit(const string caption_text);
   bool              CreateCompositeEdit(const string caption_text);
//+------------------------------------------------------------------+
//| Creates a window for creating and editing composite signals      |
//+------------------------------------------------------------------+
bool CProgram::CreateCompositeEdit(const string caption_text)
{
//--- Add a window pointer to the window array
   CWndContainer::AddWindow(m_composite_edit);
//--- Properties
   m_composite_edit.XSize(590);
   m_composite_edit.YSize(590);
//--- Coordinates
   int x=10;
   int y=10;
//---
   m_composite_edit.CaptionHeight(22);
   m_composite_edit.IsMovable(true);
   m_composite_edit.CaptionColor(m_caption);
   m_composite_edit.CaptionColorLocked(m_caption);
   m_composite_edit.CaptionColorHover(m_caption);
   m_composite_edit.BackColor(m_background);

   m_composite_edit.FontSize(m_base_font_size);
   m_composite_edit.Font(m_base_font);
   m_composite_edit.WindowType(W_DIALOG);
//--- Create the form
   if(!m_composite_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
   return(true);
}

さて、メインインターフェイスの作成メソッドにメソッドコールを追加します。

bool CProgram::CreateGUI(void)
{
//---
   ChangeLanguage();
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow(m_lang[0]))
      return(false);
//---
   if(!CreateSetWindow(m_lang[17]))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow(m_lang[39]))
      return(false);
//--- Creating a quick edit form
   if(!CreateFastEdit(m_lang[40]))
      return(false);
//---
   if(!CreateCompositeEdit(m_lang[46]))
      return(false);
//--- Complete GUI creation
   CWndEvents::CompletedGUI();
   return(true);
}

作成されたウィンドウはダイアログであるため、「コンポジットシグナルの追加」ボタンをクリックしたときに開かなければなりません。 OpenCompositeSignalEditor()メソッドを作成し、デモンストレーションに1つのコマンドを本体に追加します。 このメソッドは、イベントハンドラ内で呼び出されなければなりません。

void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      m_composite_edit.OpenWindow();
   }
}

図7は、ボタンをクリックすることで開く追加ダイアログボックスを示します。

図7 合成シグナル作成のダイアログウィンドウの追加

ここで、図2に示すようなUI要素でダイアログウィンドウを埋める必要があります。

すべての新しいメソッドは CProgram 基底クラスに追加され、CSetWindow.mqh ファイルに実装されます。 このメソッドは、同じファイル内のダイアログ作成メソッド本体に適用されます。 まず、CreateCSignalName()のインプットフィールドメソッドを作成し、を実装してダイアログウィンドウに追加します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_composite_edit);
//--- Properties
   text_edit.XSize(110);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().XGap(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[45]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[45],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(4,text_edit);
   return(true);
}
新しい CreateSimpleSignal() メソッドを使用して、2 つの状態のボタンのセットを作成します。 合成シグナルのスロット(図2)に追加されるシンプルなシグナルを選択するために使用します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleSignal(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=clrDodgerBlue;
   color pressed=clrCrimson;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BackColorLocked(clrWhiteSmoke);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.BorderColorLocked(clrWhiteSmoke);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorLocked(clrWhiteSmoke);
   button.IsCenterText(true);
   button.TwoState(true);
//--- Create a control element
   if(!button.CreateButton(" — ",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

CreateCompositeEdit()メソッドは以下のようになります。

....
//--- Signal name
   if(!CreateCSignalName(m_c_signal_name,10,40))
      return(false);
//--- Create a simple signal selection list
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(i<5)
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*i,90))
            return(false);
      }
      else
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*(i-5),90+45))
            return(false);
      }
   }
...

ボタンには2つの状態があります:使用済みのシンプルなシグナル(赤)と未使用のシグナル(青)です。 しかし、これはやはりテンプレートです。

図8 シンプルなトレードシグナルを選択するためのテンプレート

次にテンプレートに以下のロジックを適用します。

条件をすべて満たすために、正しく選択されたシグナルのみを表示する特別な検証メソッド LoadForCompositeSignal() を作成します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadForCompositeSignal(void)
{
   int cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      m_simple_signal[i].IsLocked(true);
      m_simple_signal[i].Update(true);
   }
//--- Check if there are at least two available signals with the required attributes
   for(int i=0; i<SIMPLE_SIGNALS; i++)
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
         cnt++;
//---
   if(cnt<2)
      return(false);
   else
      cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].IsLocked(false);
         cnt++;
      }
   }
//---
   cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].LabelText(m_signal_label[i].LabelText());
         m_simple_signal[cnt].Update(true);
         cnt++;
      }
   }
   return(true);
}

先に作成したOpenCompositeSignalEditor()メソッドで適用する必要があります。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      if(!LoadForCompositeSignal())
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Необходимо минимум два простых сигнала для создания составного!","Внимание");
         else
            MessageBox("You need at least two simple signals to create a composite!","Warning");
      }
      else
      {
         m_composite_edit.X(m_add_signal[1].X());
         m_composite_edit.Y(m_add_signal[1].Y()+40);
         m_composite_edit.OpenWindow();
         //---
         m_c_signal_name.SetValue("");
         m_c_signal_name.Update(true);
         //--- Clear signal selection
         for(int i=0; i<SIMPLE_SIGNALS; i++)
         {
            m_simple_signal[i].IsPressed(false);
            m_simple_signal[i].Update(true);
         }
      }
   }
}

その結果(図9)、条件を満たすシグナルを正しく選択することができました。 このように、テスト2という名前のシンプルなシグナルは、S(シンプル)属性を有するため、合成シグナルの作成には選択されません。

図9 コンポジット1で使用するシンプルなシグナルの選択

ここで、選択されたシンプルなシグナルから合成シグナルを形成するシステムを追加する必要があります。 まずはUI要素から見ていきましょう。 2種類の要素があるでしょう。 

似たような演算子であるORと(OR)は、実は違うものです。 ANDと組み合わせたOR論理演算子は、3つの式を接続すると、さまざまな解釈が可能です。 図10は、括弧によって論理式の結果が異なる場合の例を示します。


図10 (OR)と(OR)の差を示します。

コンポジットシグナルの作成・編集画面にスロットと論理演算子を追加してみましょう。 スロットを作成するためのCreateRuleSlot()メソッドを実装します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSlot(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   //color baseclr=C'70,180,70';
   color baseclr=clrLightSteelBlue;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

論理演算子の場合は、CreateRuleSelector()メソッドを作成します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSelector(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'75,190,240';
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(40);
   button.YSize(40);
   button.Font(m_base_font);
   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(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton("AND",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

CreateCompositeEdit()メソッドのダイアログ・ウィンドウの実装に、2つのメソッドを追加します。

...
//--- Header Rule
   if(!CreateSetLabel1(m_c_set_header[0],10,90+45*2,"1."+m_lang[24]))
      return(false);
//--- CreateSLots for creating a composite signal
   for(int i=0; i<3; i++)
      if(!CreateRuleSlot(m_rule_element[i],"Slot "+string(i+1),57+10+173*i,int(90+45*2.5)))
         return(false);
//--- Create logical operators
   for(int i=0; i<2; i++)
      if(!CreateRuleSelector(m_rule_selector[i],189+173*i,int(90+45*2.5)))
         return(false);
...

このステップでは、コンポジットシグナルロジックを作成するブロックの視覚的な部分は次のようになります。

図11 合成シグナル論理作成用インタフェースの視覚部

論理スイッチを有効にするには、LogicSwitch()メソッドをインプットして実装し、イベントハンドラに追加します。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::LogicSwitch(long lparam)
{
   for(int i=0; i<2; i++)
   {
      if(lparam==m_rule_selector[i].Id())
      {
         if(m_rule_selector[i].LabelText()=="OR")
            SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         else if(m_rule_selector[i].LabelText()=="(OR)")
            SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         else if(m_rule_selector[i].LabelText()=="AND")
            SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
      }
   }
}

ここで、上記のスロットを埋めることに関連するもう一つの重要なポイントに移りましょう。 必要な順番にどうやって埋めていくのか? 利用可能なシグナルのリストからシンプルなシグナルを選択してクリックします。 最初のスロットの色が青に変わり、追加されたシグナルの名前が表示されます。 2つ目のシグナルをクリックすると、2つ目のスロットが埋まります。 スロットが埋まっていて、リストの中で既に追加されているシグナルをクリックすると、占有されているスロットがインプットされます。 このメカニズムを実装するには、SignalSelection()メソッドを作成し、イベント・ハンドラに追加します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSelection(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_simple_signal[i].Id())
      {
         //--- If the signal is selected
         if(m_simple_signal[i].IsPressed())
         {
            //--Find the first freeSLot
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].BackColor()==clrLightSteelBlue)
               {
                  SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue);
                  break;
               }
            }
         }
         //--- If no signal is selected
         else
         {
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText())
               {
                  SetButtonParam(m_rule_element[j],"Slot "+string(j+1),clrLightSteelBlue);
                  break;
               }
            }
         }
      }
   }
}

以上の動作を図12に視覚的に示します。 

図12 簡易シグナルを用いた複合売買シグナルの作成例

あとは、コンポジットシグナルのモニター表示を設定するためのオプションを追加する必要があります。 オプションはコンポジットシグナル作成ウィンドウに追加されます。 簡単なシグナル作成ウィンドウにあるような要素のセクションを作成します。 設定作成の原則については、第2回の記事で検討しました。 下図13は、合成シグナル編集ウィンドウの最終的な外観を示す図です。

図13 合成シグナル作成ウィンドウの最終版

ビジュアル部分を用意しました。 ここでは計算コンポーネントを統合する必要がありますが、そにコンポジットシグナルの保存・編集機構を使用します。 必要なパラメータが設定されると(図13に示すように)、「追加」ボタンをクリックすると、指定されたパラメータがファイルに保存されます。 上記関数を実装した関数を以下に追加します。 SaveCompositeSignalSet()です。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveCompositeSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckCSignalNames(m_c_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>COMPOSITE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(COMPOSITE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_c_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_c_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_c_signal_name.GetValue(),m_c_signal_set[index].signal_name);
//---SLot values
   if(!CheckCorrectSlots(m_rule_element[0].LabelText(),m_rule_element[1].LabelText(),m_rule_element[2].LabelText()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Неверно установлено правило","Монитор сигналов");
      else
         MessageBox("Invalid rule","Signal Monitor");
      FileClose(h);
      return(false);
   }
   StringToCharArray(m_rule_element[0].LabelText(),m_c_signal_set[index].slot_1);
   StringToCharArray(m_rule_element[1].LabelText(),m_c_signal_set[index].slot_2);
   StringToCharArray(m_rule_element[2].LabelText(),m_c_signal_set[index].slot_3);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      if(m_rule_selector[i].LabelText()=="AND")
         m_c_signal_set[index].logics[i]=1;
      else if(m_rule_selector[i].LabelText()=="OR")
         m_c_signal_set[index].logics[i]=2;
      else if(m_rule_selector[i].LabelText()=="(OR)")
         m_c_signal_set[index].logics[i]=3;
   }
//--- Text label value
   StringToCharArray(StringSubstr(m_c_text_box.GetValue(),0,3),m_c_signal_set[index].label_value);
//--- Color of the text label
   m_c_signal_set[index].label_color=m_c_color_button[0].CurrentColor();
//--- Backdrop color
   if(m_c_set_param[0].IsPressed())
      m_c_signal_set[index].back_color=m_c_color_button[1].CurrentColor();
   else
      m_c_signal_set[index].back_color=clrNONE;
//--- Border color
   if(m_c_set_param[1].IsPressed())
      m_c_signal_set[index].border_color=m_c_color_button[2].CurrentColor();
   else
      m_c_signal_set[index].border_color=clrNONE;
//--- Hint value
   m_c_signal_set[index].tooltip=m_c_set_param[2].IsPressed();
   if(m_c_signal_set[index].tooltip)
      StringToCharArray(m_c_tooltip_text.GetValue(),m_c_signal_set[index].tooltip_text);
//--- Selected image
   m_c_signal_set[index].image=m_c_set_param[3].IsPressed();
   if(m_c_signal_set[index].image)
      m_c_signal_set[index].img_index=m_c_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//---
   FileWriteStruct(h,m_c_signal_set[index]);
   FileClose(h);
   Print("Конфигурация "+m_c_signal_name.GetValue()+" успешно сохранена");
//---
   return(true);
}

m_c_signal_set変数が存在しないことと、CheckCSignalNames()CheckCorrectSlots()の2つのチェックメソッドが存在しないことです。 変数型は、コンポジットシグナルパラメータのセットを格納するために作成された新しいC_SIGNAL構造体です。

struct C_SIGNAL
{
   uchar            SLot_1[50];
   uchar            SLot_2[50];
   uchar            SLot_3[50];
   int               logics[2];
   uchar             signal_name[50];
   uchar             label_value[10];
   color             label_color;
   color             back_color;
   color             border_color;
   bool              tooltip;
   uchar             tooltip_text[100];
   bool              image;
   int               img_index;
};

CheckCSignalNames() メソッドは CheckSignalNames() と似ています。 新しいCheckCorrectSlots()メソッドは、コンポジットシグナルの作成された論理構成の完全性と正確性をチェックします。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckCorrectSlots(string name1,string name2,string name3)
{
   boolSLot1=(name1=="Slot 1")?true:false;
   boolSLot2=(name2=="Slot 2")?true:false;
   boolSLot3=(name3=="Slot 3")?true:false;
   int cnt=0;
   //---
   if(slot1)
      return(false);
   if(slot2 && !slot3)
      return(false);
   //---
   if(!slot1)
      cnt++;
   if(!slot2)
      cnt++;
   if(!slot3)
      cnt++;
   if(cnt<2)
      return(false);
   return(true);
}

新しいコンポジットシグナルを作成するために必要な関数が準備できたので、AddCompositeSignal()メソッドを開発します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddCompositeSignal(long lparam)
{
   if(lparam==m_c_new_signal.Id())
   {
      if(m_c_number_signal<0)
      {
         if(SaveCompositeSignalSet(m_c_total_signals))
         {
            m_composite_edit.CloseDialogBox();
            if(m_c_total_signals<COMPOSITE_SIGNALS)
            {
               m_c_total_signals++;
               m_c_signal_label[m_c_total_signals-1].Show();
               m_c_signal_label[m_c_total_signals-1].LabelText(m_c_signal_name.GetValue());
               m_c_signal_label[m_c_total_signals-1].Update(true);
               m_c_signal_ind[m_c_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of composite signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
         }
      }
      else
      {
         if(SaveCompositeSignalSet(m_c_number_signal))
         {
            m_composite_edit.CloseDialogBox();
            m_c_signal_label[m_c_number_signal].LabelText(m_c_signal_name.GetValue());
            m_c_signal_label[m_c_number_signal].Update(true);
         }
      }
   }
}

作成したトレーディングシグナルを編集する関数を作る前に、ファイルからコンポジットシグナル編集画面に設定をアップロードする仕組みを追加してみましょう。 LoadCompositeSignalSet()メソッドによって行われます。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadCompositeSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_c_signal_set[index]);
   FileReadStruct(h,m_c_signal_set[index]);
//--- Loading the indicator name
   m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name));
   m_c_signal_name.GetTextBoxPointer().Update(true);
//---SLot values
   stringSLot_1=CharArrayToString(m_c_signal_set[index].slot_1);
   stringSLot_2=CharArrayToString(m_c_signal_set[index].slot_2);
   stringSLot_3=CharArrayToString(m_c_signal_set[index].slot_3);
   color back=clrDodgerBlue;
   if(slot_1=="Slot 1")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[0],slot_1,back);
   if(slot_2=="Slot 2")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[1],slot_2,back);
   if(slot_3=="Slot 3")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[2],slot_3,back);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      switch(m_c_signal_set[index].logics[i])
      {
      case  1:
         SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         break;
      case  2:
         SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         break;
      case  3:
         SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
         break;
      default:
         break;
      }
   }
//--- Loading a text label
   m_c_text_box.ClearTextBox();
   m_c_text_box.AddText(0,CharArrayToString(m_c_signal_set[index].label_value));
   m_c_text_box.Update(true);
//--- Loading the color of the text label
   m_c_color_button[0].CurrentColor(m_c_signal_set[index].label_color);
   m_c_color_button[0].Update(true);
//--- Loading the background color
   if(m_c_signal_set[index].back_color==clrNONE)
   {
      m_c_set_param[0].IsPressed(false);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(true);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[0].IsPressed(true);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(false);
      m_c_color_button[1].CurrentColor(m_c_signal_set[index].back_color);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_c_signal_set[index].border_color==clrNONE)
   {
      m_c_set_param[1].IsPressed(false);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(true);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[1].IsPressed(true);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(false);
      m_c_color_button[2].CurrentColor(m_c_signal_set[index].border_color);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_c_signal_set[index].tooltip)
   {
      m_c_set_param[2].IsPressed(false);
      m_c_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(true);
      m_c_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(false);
      m_c_tooltip_text.ClearTextBox();
      m_c_tooltip_text.AddText(0,CharArrayToString(m_c_signal_set[index].tooltip_text));
      m_c_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_c_signal_set[index].image)
   {
      m_c_set_param[3].IsPressed(false);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(true);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_c_set_param[3].IsPressed(true);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(false);
      m_c_pictures_slider.GetRadioButtonsPointer().SelectButton(m_c_signal_set[index].img_index);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//---
   FileClose(h);
   return(true);
}

新しいEditCompositeSignal()メソッドを使って、作成されたシグナルを編集する関数を追加することができます。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditCompositeSignal(long lparam)
{
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(lparam==m_c_signal_ind[i].Id())
      {
         LoadCompositeSignalSet(i);
         m_c_new_signal.LabelText(m_lang[38]);
         m_c_new_signal.Update(true);
         m_composite_edit.OpenWindow();
         m_c_number_signal=i;
         for(int j=0; j<SIMPLE_SIGNALS; j++)
            m_simple_signal[j].IsLocked(true);
      }
   }
}

コンポジットシグナルの作成、保存、編集、読み込み関数のソフトウェア実装が完了しました。 アプリケーション開発の直近のステップは、作成したコンポジットシグナルをモニターに追加することです。 以前のバージョンではSearchSignals()メソッドで行われていたので、修正してください。 メソッドコードは、理解の容易さと明瞭さを最大限に確保するように設計されており、追加の補助メソッドを導入することで実現されています。 にもかかわらず、シンプルなシグナルの探索と合成シグナルの探索という2つの論理ブロックに分かれます。 ここでは、シンプルなシグナルの探索に専念する第1部を考えてみましょう。 コードの変更に注意してください。

//+------------------------------------------------------------------+
//| Signal search                                                    |
//+------------------------------------------------------------------+
bool CProgram::SearchSignals(void)
{
//--- Search for the number of created simple signals
   int cnt1=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt1++;
   }
//--- Search for simple signals
   SIGNAL signal_set;
   ZeroMemory(signal_set);
   for(int i=0; i<cnt1; i++)
   {
      //--- Skip a signal if it is only set for use in composite signals
      if(m_signal_type[i].LabelText()=="C")
         continue;
      //---
      if(GetSimpleSignal(signal_set,i))
         return(false);
      //---
      for(int j=0; j<ArraySize(m_signal_button); j++)
      {
         //---
         string sy=GetSymbol(j);
         ENUM_TIMEFRAMES tf=GetTimeframe(j);
         //---
         if(!CheckTimeframe(tf,signal_set))
            continue;
         //---
         if(GetSignal(sy,tf,signal_set))
            SetVisualSignal(signal_set,j);
         else
            SetDefaultVisual(j);
      }
   }
.....

先にシンプルなシグナルの使用属性を紹介したので、シンプルなシグナルのフィルタリングを追加して、合成シグナルの一部としてのみ使用するのをスキップする必要があります 。 指定されたファイルから各シンプルシグナルの設定を取得して構造体に書き込むためのGetSimpleSignal()シグナルを追加しました。 さらに2つの新しいメソッド、SetVisualSignal()SetDefaultVisual()は、現在検出されたシグナルに応じて指定されたビジュアル設定の表示を実装します。 シグナルがない場合は、デフォルトのシグナルブロックが表示されます。 新しいシンプルシグナル探索の実装を旧バージョンと比較してみると、新バージョンの方がわかりやすく、3倍近く短くなっています。

では、シグナル検索方法の第2部である、複合売買シグナルを探すメソッドに移ります。

.....
//--- Search for composite signals
   C_SIGNAL c_signal_set;
   ZeroMemory(c_signal_set);
   int cnt2=0;
   int signal_number[3];
   ArrayInitialize(signal_number,-1);
//--- Search for the number of created composite signals
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\c_signal_"+string(i)+".bin"))
         cnt2++;
   }
//-- Exit if there are no signals
   if(cnt2<1)
      return(true);
//--- Search for configurations with composite signals
   for(int i=0; i<cnt2; i++)
   {
      //---
      int h=FileOpen("Signal Monitor\\c_signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      ZeroMemory(c_signal_set);
      FileReadStruct(h,c_signal_set);
      FileClose(h);
      //--- Search for simple signals used in the composite one (C or B)
      for(int m=0; m<cnt1; m++)
      {
         if(m_signal_type[m].LabelText()!="S")
            GetSimpleSignal(signal_set,m);
         //---
         if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_1))
            signal_number[0]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_2))
            signal_number[1]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_3))
            signal_number[2]=m;
      }
      ArrayPrint(signal_number);
   }
//---
   int used_slots=GetUsedSlots(CharArrayToString(c_signal_set.slot_1),CharArrayToString(c_signal_set.slot_2),CharArrayToString(c_signal_set.slot_3));
//---
   for(int j=0; j<ArraySize(m_signal_button); j++)
   {
      //---
      string sy=GetSymbol(j);
      ENUM_TIMEFRAMES tf=GetTimeframe(j);
      //---
      GetSimpleSignal(signal_set,signal_number[0]);
      bool sig1=GetSignal(sy,tf,signal_set);
      GetSimpleSignal(signal_set,signal_number[1]);
      bool sig2=GetSignal(sy,tf,signal_set);
      if(used_slots==2)
      {
         //--- AND
         if(c_signal_set.logics[0]==1)
            if(sig1 && sig2)
               SetVisualCompositeSignal(c_signal_set,j);
         //--- OR
         if(c_signal_set.logics[0]>1)
            if(sig1 || sig2)
               SetVisualCompositeSignal(c_signal_set,j);
      }
      else if(used_slots==3)
      {
         GetSimpleSignal(signal_set,signal_number[2]);
         bool sig3=GetSignal(sy,tf,signal_set);
         //--- AND OR
         if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==2)
         {
            if((sig1 && sig2) || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND (OR)
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==3)
         {
            if(sig1 && (sig2 || sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR AND
         else if(c_signal_set.logics[0]==2 && c_signal_set.logics[1]==1)
         {
            if(sig1 || (sig2 && sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- (OR) AND
         else if(c_signal_set.logics[0]==3 && c_signal_set.logics[1]==1)
         {
            if((sig1 || sig2) && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND AND
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==1)
         {
            if(sig1 && sig2 && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR OR
         else if(c_signal_set.logics[0]>1 && c_signal_set.logics[1]>1)
         {
            if(sig1 || sig2 || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
      }
   }
   return(true);
}

また、合成シグナルの検索には、チェックとモニターでの可視化の両方の方法を追加する必要がありました。 チェックメソッドはGetUsedSlots() です. 検索システムで使用する合成シグナルの種類を決定します:2つまたは3つのシンプルなシグナルで構成されているかどうか。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CProgram::GetUsedSlots(string name1,string name2,string name3)
{
   int cnt=0;
   if(name1!="Slot 1")
      cnt++;
   if(name2!="Slot 2")
      cnt++;
   if(name3!="Slot 3")
      cnt++;
   return(cnt);
}

第2のメソッドSetVisualCompositeSignal()は、シグナルブロック内で見つかった合成シグナルを、現在の合成シグナルの視覚的パラメータのセットとして表示します。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SetVisualCompositeSignal(C_SIGNAL &signal_set,int block)
{
   //---
   SetLabel(block,CharArrayToString(signal_set.label_value),signal_set.label_color);
   //---
   if(signal_set.back_color!=clrNONE)
      SetBackground(block,signal_set.back_color);
   //---
   if(signal_set.border_color!=clrNONE)
      SetBorderColor(block,signal_set.border_color);
   else
      SetBorderColor(block,signal_set.back_color);
   //---
   if(signal_set.tooltip)
      SetTooltip(block,CharArrayToString(signal_set.tooltip_text));
   //---
   if(signal_set.image)
      SetIcon(block,signal_set.img_index);
   else
      SetIcon(block,-1);
}

シグナルモニターは完全に完成しました。 将来的には、ユーザーからの提案を実装したり、追加関数を追加したりするために、さらに修正や改良を加えることができます。

このシリーズの以前の記事

結論

添付されているアーカイブには、リストされているすべてのファイルが含まれており、適切なフォルダに配置されています。 正常に動作させるためには、MQL5フォルダをターミナルフォルダに保存するだけです。 MQL5フォルダが配置されているターミナルのルートディレクトリを開くには、MetaTrader5ターミナルでCtrl+Shift+Dキーの組み合わせを押すか、以下の図14に示すようにコンテキストメニューを使用します。


図14 MetaTrader5のターミナルルートでMQL5フォルダを開く