Multicurrency monitoring of trading signals (Part 5): Composite signals
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:
- Multicurrency monitoring of trading signals (Part 1): Developing the application structure
- Multicurrency monitoring of trading signals (Part 2): Implementation of the visual part of the application
- Multicurrency monitoring of trading signals (Part 3): Introducing search algorithms
- Multicurrency monitoring of trading signals (Part 4): Enhancing functionality and improving the signal search system
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
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
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