Русский 中文 Español Deutsch 日本語 Português
Multicurrency monitoring of trading signals (Part 5): Composite signals

Multicurrency monitoring of trading signals (Part 5): Composite signals

MetaTrader 5Trading | 21 August 2020, 13:27
13 260 1
Alexander Fedosov
Alexander Fedosov

Contents

Introduction

In the previous part of the article series, devoted to the creation of a multicurrency trading signal monitor, we introduced the possibility to use custom indicators and extended the system of logical rules allowing to filter data. However, many traders and developers use trading systems based on multiple indicators. Each of the indicator types, including trend, flat and oscillators, have their disadvantages in certain markets.

For example, during prolonged sideways movement, trend indicators often produce a lot of false signals. Thus, they need to be confirmed by some other tools. This can be done by adding more indicators, in addition to the main one, which serve as filters for signals generated by the main indicator. Optionally, traders can create a trading system based on a set of indicators which show the same market states. In this case, the market entry signal can be generated by simultaneous confirmations form several indicators.

Therefore, the next logical step in our indicator development is the introduction of composite signals made up of available simple ones. Thus, we will work with the concept of a composite signal. We will use a project from the previous part as the basis. The project is available at the end of the previous article.


Composite Signals

To begin with, let us define the concept of a "composite signal", which will be used in our project. In earlier versions we used simple signals, which were created by applying specific conditions to the received source, which could be presented by various indicators, such as RSI, WPR and CCI. Also, the possibility to use custom indicators was implemented.

A composite signal is a signal consisting of two or more simple signals which are connected with each other by logical AND/OR operators.

Thus, the composite signal will include several previously created simple signals, which will interact with each other using logical operators. It will be possible to create a complex signal containing the condition for the presence of two or three simultaneous simple signals in a given period of time. Thus, the trading system will have one main signal and a filter. The logical OR operator will allow us to search for a trading signal in several directions at once and thus to cover a larger area in analyzing current market states.

For a better explanation, here is some plan for updating and enhancing our applications. First of all, we need to update the application interface and add new controls.


Fig. 1. Adding UI elements

The left part of Figure 1 is already familiar to you. It is a button for adding a signal, followed by the already added simple signals. Now it is necessary to create a button for adding a composite signal (1), a list of added composite signals (2), and a third group of switch buttons (3) allowing to set the usage flag to simple signals:

  • S(Simple) — a simple signal. It is used as an independent signal in the monitor and it cannot be selected in a composite signal.
  • C(Composite) — a simple signal which can only be applied as a part of composite signal. It cannot be used in a monitor as an independent signal
  • B(Both) — both application types. Such a signal can be searched as an independent one in the monitor and can be used as a part of a composite signal.

A composite signal creation scheme is shown below in figure 2. Elements named Simple 1 and Simple 2 show a list of already created simple signals which can be used in the creation of a complex one. As for the signal usage attributes, the signals shown in figure 2 should have an attribute "C" or "B", otherwise they would not be shown in the list.

Fig.2 The scheme of the composite signal creation and editing window

This is followed by a section called Rule, in which the composite signal is created. A signal form the above lists can be added to any of the three lots. Logical operators between the slots are selected using buttons. This sets a rule for this composite signal. For example, if you set Simple 1 AND Simple 2, a composite signal will only be displayed when these two simple signals appear at the same time.


Implementing the Functionality

Prior to introducing new additions in the application, it is necessary to prepare the existing elements to be used with new functions. The UI elements of the list of simple signals need to be changed. Figure 3 shows that a simple button with the system name of a simple signal will be replaced by a text label with a user-specified signal name, as well as an Edit button and a button for the signal usage flag.


Fig. 3. Updating the signal editing control

Now, open the project downloaded from the previous article and proceed with the additions. Firstly, remove the CreateSignalEditor() method and the m_signal_editor[] array which is used for creating edit buttons. Before proceeding to creating new elements and methods, let's introduce two macro substitutions that will determine the maximum allowed number of simple and composite signals.

#define SIMPLE_SIGNALS 10
#define COMPOSITE_SIGNALS 5

 Next, go to the end of the CreateStepWindow() main window creating method body and instead of the following code:

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

Add a new implementation for the display and editing of a new signal.

   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);
   }

The implementation has two new arrays of CButton class instances: m_signal_ind[] and m_signal_type[], as well as the CTextLabel m_signal_label[] array. Add them to the project's base class CProgram.

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

Also, declare and implement the new CreateSignalSet() method.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

Thus, we have formed a list of simple signals with the updated interaction interface from figure 3. A list of composite signals is added by a simple addition of variables m_c_signal_label[] and m_c_signal_ind[] to the base class and to the CreateStepWindow() method of the below code.

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);
   }

The tool for displaying and editing simple and composite signals have been created anew. Now, in Step 3 of the initial application setup, add a button for creating composite signals, along with a text header. This will be done by making a static array m_add_signal[2] out of the m_add_signal variable, which is an instance of the CButton class and is used in the AddSignal button. Replace the following code in the CreateStepWindow() method body

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

with a new one containing a composite signal creation button:

   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);

To display the headers of lists of simple and composite signals, turn the m_signal_header variables into the m_signal_header[2] static array. The previous code:

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

should be replaced with a new one having two headers:

   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);

After all the above changes, the updated interface in Step 3 looks as follows:

Fig.4 Adding and updating buttons for creating trading signals 

Now, we need to link the earlier created objects of the list of simple trading signals with the creation and editing event. Here I add a short notice. Since the number of UI elements is growing, causing the number of possible user interactions also to grow, the OnEvent() handler body becomes too long and it becomes difficult to understand to which element an event or a group of events belongs. That is why I decided to wrap all key interactions with the UI in appropriate methods. This provides two advantages: OnEvent() will have the list of key events, so logic and code of each of them can be easily accessed.

Find the code fragment responsible for the addition of a new simple trading signal, add the required code and move it to the AddSimpleSignal() method:

//+------------------------------------------------------------------+
//| 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);
         }
      }
   }
}

Call it in the OnEvent() handler. Thus we reduce the size of the method body and will structure its implementation part. Figure 3 shows that the new implementation should support the possibility to set a custom signal name. This can be done by adding the following field in the simple signal creation and editing window. Create a new method called CreateSignalName(), which will add an input field for the signal name.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

Also, do not forget to add the input field for the simple signal name to the SIGNAL structure.

uchar             signal_name[50];

Methods SaveSignalSet() and LoadSignalSet() should also be updated, so that they record the signal name in addition to the existing settings. When creating a new signal, we should avoid the situation when a new signal name matches the one of an earlier created signal. Another situation to be avoided is when parameters are saved to the file of an earlier created signal. Thus, add the second first_save argument of the SaveSignalSet() method. It will serve as an indication of whether a signal is saved for the first time or edited parameters of an earlier created signal are saved.

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

The full implementation of the method looks as follows:

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 commas","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);
}

In the above code, I highlighted the check for the sign of a new signal and the CheckSignalNames() method which checks if a signal with the specified name already exists. Further code checks if there is a signal name and if its length is at least three characters.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

Now, we can create simple trading signals in the new format, as it is shown in the below screenshot. Let's activate the created signal editing buttons and see how a usage sign is assigned to a simple signal.

Fig.5 New format of created simple trading signals 

To enable editing of simple signals, create a new EditSimpleSignal() method and call it in the OnEvent() handler. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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;
      }
   }
}

Similarly, to switch the sign of usage, create a new SignalSwitch() method and call it in the handler.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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');
      }
   }
}

The switch is shown in figure 6 below. It is performed by a simple button click.

Fig.6 Visual switch of simple signal usage sign

Trading signals are now ready for use as logical elements of composite trading signals.

We have previously added a button creating a new composite signal - 'Add Composite Signal'. Now we need to implement a dialog window allowing to create, set up and edit composite signals. Earlier, when creating a simple signal editing window, we created an include file SetWindow.mqh. So now we create CSetWindow.mqh, in which the UI of the future composite signal editor will be created. Open the newly created file and add Program.mqh to access the CProgram base class.

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

In the Program.mqh file, connect CSetWindow.mqh to existing controls:

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

To create a dialog window, we introduce a new method in the CreateCompositeEdit() base class and implement it in the newly created include file.

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);
}

Now, we add the method call in the main interface creation method.

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);
}

The created window is a dialog and thus it should open upon a certain event, which is a click on the 'Add Composite Signal' button. We create the OpenCompositeSignalEditor() method and add one command to its body for demonstration purposes. The method must be called in the event handler.

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

Figure 7 shows the added dialog box which opens by a click on the button.

Fig.7 Adding a dialog window for creating composite signals

Now, we need to fill the dialog window with the UI elements shown in figure 2:

  • Field for entering the composite signal name.
  • The list of available simple signals for creating a composite one.
  • Interface for creating a logical rule of a composite signal.

All new methods will be added to the CProgram base class and will be implemented in the CSetWindow.mqh file. The method will be applied in the same file, in the dialog creation method body. Firstly, we create the CreateCSignalName() input field method, implement it and add to the dialog window.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}
Using the new CreateSimpleSignal() method, create a set of two-state buttons. They will be used to select simple signals to be added to slots (fig.2) for a composite signal.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

The CreateCompositeEdit() method is as follows:

....
//--- 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);
      }
   }
...

Buttons have two states: a used simple signal (red) and an unused signal (blue). But this is still a template.

Fig. 8 Template for selecting simple trading signals

The next step is to apply the following logic to the template:

  • When 'Add Composite Signal' is pressed, it is necessary to check if at least two created signals are available.
  • Moreover, the list of already created simple signals must have at least two signals with either C (Composite) or B (Both) attribute.
  • If the above conditions are met, show available simple signals which can be used to form a composite one.

To fulfill all these conditions, create a special verification method LoadForCompositeSignal(), which will only display correctly selected signals.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

It should be applied in the earlier created OpenCompositeSignalEditor() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
         }
      }
   }
}

As a result (fig 9) we have a correct selection of signals meeting our conditions. As can be seen, the simple signal named Test 2 is not selected for the creation of a composite signal, because it has the S (Simple) attribute.

Fig. 9 Selecting simple signals to be used in a composite one

Now, we need to add the system forming a composite signal out of selected simple signals. Let's begin with the UI elements. There will be two types of elements: 

  • Slots. Three slots for placing selected simple signals.
  • Logical operators. Two toggle buttons with three states AND, OR and (OR).

The two similar operators, OR and (OR), are actually different. The OR logical operator combined with AND can be interpreted in different ways, when they connect three expressions. Figure 10 shows an example, where the result of a logical expression can be different depending on the brackets.


Fig. 10 The difference between OR and (OR)

Let's add slots and logical operators to the composite signal creating and editing window. Implement the CreateRuleSlot() method for creating slots:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

For logical operators, create the CreateRuleSelector() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

Add these two methods to the dialog window implementation in the CreateCompositeEdit() method.

...
//--- Header Rule
   if(!CreateSetLabel1(m_c_set_header[0],10,90+45*2,"1."+m_lang[24]))
      return(false);
//--- Create slots 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);
...

At this step, the visual part of the block creating composite signal logic looks like this:

Fig. 11 Visual part of the interface for creating the composite signal logic

To enable logical switches, enter the LogicSwitch() method, implement it and add to the event handler. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
      }
   }
}

Now let us move to another important point related to filling of the above slots. How to fill them in in the required sequence? Choose a simple signal from the list of available signals and click on it. The color of the first slot will change to blue and will display the name of the added signal. A click on the second signal will fill the second slot. If slots are filled and you click on the already added signal in the list, the slot occupied by it will be released. To implement this mechanism, create the SignalSelection() method and add it to the event handler:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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 free slot
            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;
               }
            }
         }
      }
   }
}

The above actions are visually shown in figure 12. 

Fig. 12 An example of creating a composite trading signal using simple signals

Now, we need to add options for setting up the display of the composite signal in the monitor. The options will be added to the composite signal creation window. We will create a section of elements similar to the one in the simple signal creation window. The settings creation principles were considered in the second article. Fig. 13 below shows the final appearance of the composite signal editing window.

Fig. 13 The final version of the composite signal creating window

We have prepared the visual part. Now we need to integrate the calculation component, for which we will use composite signal saving and editing mechanism. Once the required parameters are set (as is shown in Fig.13), a click on the Add button saves the specified parameters to a file. The function implementing the above functionality is added below. It is called 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);
}

An attempt to compile the project now will produce three major errors: absence of the m_c_signal_set variable and of two check methods — CheckCSignalNames() and CheckCorrectSlots(). The variable type is the new C_SIGNAL structure created for storing a set of composite signal parameters:

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;
};

The CheckCSignalNames() method us very similar to CheckSignalNames(): it checks whether the specified composite signal name is unique. The new CheckCorrectSlots() method checks the integrity and correctness of the created logical construction of the composite signal:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckCorrectSlots(string name1,string name2,string name3)
{
   bool slot1=(name1=="Slot 1")?true:false;
   bool slot2=(name2=="Slot 2")?true:false;
   bool slot3=(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);
}

The necessary functionality for creating a new composite signal is ready, so now we will develop the AddCompositeSignal() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
         }
      }
   }
}

Before creating the functionality for editing the created trading signal, let's add a mechanism for uploading settings to the composite signal editing window from a file. This will be done by the LoadCompositeSignalSet() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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
   string slot_1=CharArrayToString(m_c_signal_set[index].slot_1);
   string slot_2=CharArrayToString(m_c_signal_set[index].slot_2);
   string slot_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);
}

Now we can add the possibility to edit the created signal using the new EditCompositeSignal() method:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
      }
   }
}

The software implementation of composite signal creating, saving, editing and loading functionality is ready. The last application development step is left: add the created composite signals to the monitor. In earlier versions this was performed by the SearchSignals() method, so modify it. The method code was designed so as to ensure the maximum ease of understanding and clarity — this was implemented by introducing additional auxiliary methods. Nevertheless, it will be split into two logical blocks: search for simple signals and search for composite signals. Let's consider the first part which is devoted to the search of simple signals. Pay attention to changes in the code.

//+------------------------------------------------------------------+
//| 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);
      }
   }
.....

We earlier introduced simple signal use attributes, therefore we need to add filtration of simple signals to skip those that should only be used as part of a composite signal. The GetSimpleSignal() signal was added to obtain settings for each simple signal form the specified file and to write them to the structure. Two more new methods, SetVisualSignal() and SetDefaultVisual() implement the display of specified visual setting according to the current found signal. If there is no signal, the default signal block will be displayed. If we compare the new simple signal search implementation with the old one, the new version has become easier to understand and become shorter by almost three times.

Now let's move on to the second part of the signal search method, which looks for composite trading signals.

.....
//--- 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);
}

Search for composite signals also required additional methods both for checks and for visualization in the monitor. The check method is GetUsedSlots(). It determines the type of the composite signal used in the search system: whether it consists of two or three simple signals.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

The second method SetVisualCompositeSignal() displays the found composite signal in the signal block as a set of visual parameters of the current composite signal.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}

The Signal Monitor is now completely ready. It can be further amended or refined in the future to implement suggestions of users or to add extra features.

Previous articles in this series:

Conclusion

The attached archive contains all the listed files, which are located in the appropriate folders. For their proper operation, you only need to save the MQL5 folder into the terminal folder. To open the terminal root directory, in which the MQL5 folder is located, press the Ctrl+Shift+D key combination in the MetaTrader 5 terminal or use the context menu as shown in Fig. 14 below.


Fig. 14. Opening the MQL5 folder in the MetaTrader 5 terminal root


Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/7759

Attached files |
MQL5.zip (2009.3 KB)
Last comments | Go to discussion (1)
Mahir Muhtasim Alam Khan
Mahir Muhtasim Alam Khan | 13 Sep 2022 at 22:24

A very good article only if it worked.

Three problems are present:

1- identifier 'ENUM_SORT_MODE' already used Enums.mqh 132 6

2- 'SORT_ASCEND' - improper enumerator cannot be used Table.mqh 355 88

3- 'SORT_ASCEND' - improper enumerator cannot be used Table.mqh 2226 80



Timeseries in DoEasy library (part 43): Classes of indicator buffer objects Timeseries in DoEasy library (part 43): Classes of indicator buffer objects
The article considers the development of indicator buffer object classes as descendants of the abstract buffer object simplifying declaration and working with indicator buffers, while creating custom indicator programs based on DoEasy library.
Native Twitter Client: Part 2 Native Twitter Client: Part 2
A Twitter client implemented as MQL class to allow you to send tweets with photos. All you need is to include a single self contained include file and off you go to tweet all your wonderful charts and signals.
MQL as a Markup Tool for the Graphical Interface of MQL Programs (Part 3). Form Designer MQL as a Markup Tool for the Graphical Interface of MQL Programs (Part 3). Form Designer
In this paper, we are completing the description of our concept of building the window interface of MQL programs, using the structures of MQL. Specialized graphical editor will allow to interactively set up the layout that consists of the basic classes of the GUI elements and then export it into the MQL description to use it in your MQL project. The paper presents the internal design of the editor and a user guide. Source codes are attached.
Timeseries in DoEasy library (part 42): Abstract indicator buffer object class Timeseries in DoEasy library (part 42): Abstract indicator buffer object class
In this article, we start the development of the indicator buffer classes for the DoEasy library. We will create the base class of the abstract buffer which is to be used as a foundation for the development of different class types of indicator buffers.