Die Handelssignale mehrerer Währungen überwachen (Teil 4): Erweiterung der Funktionsweise und Verbesserung des Signalsuchsystems

Alexander Fedosov | 14 Juli, 2020

Inhalt

Einführung

Im dritten Teil haben wir ein Grundsystem für die Suche nach Signalen geschaffen, das jedoch auf einem kleinen Satz von Indikatoren und einem einfachen Satz von Suchregeln beruhte. Außerdem erhielt ich Vorschläge für Verbesserungen der Nutzerfreundlichkeit, die im visuellen Teil des Handelsmonitors vorgenommen werden könnten. Das ist es, was wir in diesem Teil umsetzen werden.

Nutzerdefinierter Indikator zur Erzeugung eines Handelssignals

Eine logische Ergänzung zur Erstellung und Bearbeitung von Handelssignalen ist die Erweiterung des Satzes der verfügbaren Indikatoren. Bisher konnten wir nur mit Indikatoren aus dem Standardset von MetaTrader 5 arbeiten. Jetzt können wir die Möglichkeit hinzufügen, den Berechnungsteil von nutzerdefinierten Indikatoren zu verwenden. Lassen Sie uns das Projekt aus dem vorherigen Teil als Grundlage verwenden. Es kann aus der Anlage des Artikels heruntergeladen werden. In diesem Teil werden wir die Betriebsalgorithmen der Methoden der Basisklassen ändern müssen, die wir in Teil 3 betrachtet haben. Alle Änderungen und Ergänzungen werden mit entsprechenden Erläuterungen versehen.

Beginnen wir mit der Möglichkeit, einen nutzerdefinierten Indikator im Fenster zum Hinzufügen und Bearbeiten von Signalen auszuwählen. Diese Fensterimplementierung ist in der Datei SetWindow.mqh unseres Projekts vorgesehen. Öffnen Sie diese Datei und suchen Sie die Methode CreateIndicatorType(). Änderungen sollten genau in dieser Datei implementiert werden.

//+------------------------------------------------------------------+
//| Creates a drop-down menu with indicator types                    |
//+------------------------------------------------------------------+
bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_indicator_type.MainPointer(m_set_window);
//---
#define SIZE 10
//--- Array of the item values in the list view
   string pattern_names[SIZE]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom"
   };
//--- Set up properties before creation
   m_indicator_type.XSize(200);
   m_indicator_type.YSize(26);
   m_indicator_type.LabelYGap(4);
   m_indicator_type.ItemsTotal(SIZE);
   m_indicator_type.Font(m_base_font);
   m_indicator_type.FontSize(m_base_font_size);
   m_indicator_type.BackColor(m_background);
   m_indicator_type.GetButtonPointer().Font(m_base_font);
   m_indicator_type.GetButtonPointer().FontSize(m_base_font_size);
   m_indicator_type.GetButtonPointer().BackColor(clrWhite);
   m_indicator_type.GetButtonPointer().XGap(100);
   m_indicator_type.GetButtonPointer().XSize(100);
   m_indicator_type.GetListViewPointer().Font(m_base_font);
   m_indicator_type.GetListViewPointer().FontSize(m_base_font_size);
   m_indicator_type.GetListViewPointer().ItemYSize(25);
   m_indicator_type.GetListViewPointer().YSize(200);
//--- Save the item values in the combobox list view
   for(int i=0; i<SIZE; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_indicator_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_indicator_type.SelectItem(1);
//--- Create the control
   if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_indicator_type);
   return(true);
}

Betrachten wir nun, was sich im Vergleich zur vorherigen Version geändert hat. Zunächst haben wir Size-Makrosubstitution hinzugefügt, was die Anzahl der Elemente in der Dropdown-Liste bedeutet. Auf diese Weise können wir die Listenlänge von einer einzigen Stelle aus ändern, ohne dass wir in allen Codeteilen Ersetzungen vornehmen müssen. Fügen wir dann am Ende ein neues Listenelement hinzu: Custom. Die Änderungen sind in Abbildung 1 unten dargestellt.

Abb.1 Hinzufügen eines Elements zur Auswahl eines nutzerdefinierten Indikators.  

Fügen wir nun neue Oberflächenelemente für die Einrichtung und Verwendung des Indikators hinzu. Wir müssen Änderungen in Übereinstimmung mit den iCustom() Funktionsargumenten vornehmen, um den berechneten Teil Ihres eigenen Indikators zu verwenden. Dazu gehören der Symbolname, der Punkt, der Pfad zur kompilierten *.ex5-Indikatordatei und eine durch Komma getrennte Liste von Indikatorparametern.

int  iCustom(
   string           symbol,     // symbol name
   ENUM_TIMEFRAMES  period,     // period
   string           name        // folder/custom indicator_name
   ...                          // the list of indicator input parameters
   );

Der Symbolname und der Zeitrahmen werden durch die Werte ersetzt, die in den ersten beiden Schritten der anfänglichen Anwendungseinrichtung ausgewählt wurden. Die Nutzer müssen jedoch den Indikatorpfad und die Liste der Parameter selbst festlegen. Zu diesem Zweck müssen zwei zusätzliche Felder hinzugefügt werden. Fügen wir zwei neue Variablen und eine Methode zur Basisklasse CProgramm hinzu:

   CTextEdit         m_custom_path;
   CTextEdit         m_custom_param;

   bool              CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text);

Da die Methode im Fenster zum Erstellen/Bearbeiten von Handelssignalen angewendet wird, implementieren wir sie in der Datei SetWindow.mqh:

//+------------------------------------------------------------------+
//| Input field for a custom indicator                               |
//+------------------------------------------------------------------+
bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(100);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.GetTextBoxPointer().XSize(325);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(default_text);
   text_edit.GetTextBoxPointer().BorderColor(clrBlack);
//--- Create the control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.IsLocked(true);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Erstellen wir zwei Eingabefelder mit der Methode CreateCustomEdit(). Suchen Sie in derselben Datei im Methodenrumpf CreateSetWindow() den Abschnitt Selected indicator settings und fügen Sie den folgenden Code hinzu:

   if(!CreateCustomEdit(m_custom_path,240,22+10+2*(25+10),"Enter the indicator path"))
      return(false);
   if(!CreateCustomEdit(m_custom_param,240,22+10+3*(25+10),"Enter indicator parameters separated by commas"))
      return(false);

 Infolgedessen erscheinen im Einstellungsfenster zwei Eingabefelder, wie in Abbildung 2 dargestellt.

Abb. 2 Hinzufügen von Eingabefeldern für benutzerdefinierte Indikatoreinstellungen.

Sie sind in diesem Entwicklungsstadium inaktiv. Dies liegt daran, dass ihre Verfügbarkeit strikt vom gewählten Indikatortyp abhängt, d.h. sie sind nur verfügbar, wenn Custom in der Dropdown-Liste ausgewählt ist. Um diese Aufgabe umzusetzen, überarbeiten wir die Methode RebuildParameters(). Zunächst gehen wir aber zum Abschnitt Ereignis zur Auswahl eines Elements aus der Dropdown-Liste in der Methode OnEvent() und fügen eine Prüfung für das Ereignis der gewünschten Liste mit der Auswahl des Indikatortyps hinzu.

//--- Selecting an item in the combobox drop-down list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      //--- Indicator type
      if(lparam==m_indicator_type.Id())
         RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
   }

Ändern wir nun die Methode RebuildParameters() so, dass bei Auswahl jedes der verfügbaren Indikatoren die entsprechenden Einstellungen angezeigt werden. Außerdem würde dies für den nutzerdefinierten Indikator die Eingabefelder für Pfade und Parameter aktiv machen.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildParameters(int index)
{
   switch(index)
   {
   case  0:
      m_period_edit.LabelText("ATR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  1:
      m_period_edit.LabelText("CCI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  2:
      m_period_edit.LabelText("DeMarker Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  3:
      m_period_edit.LabelText("Force Index Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  4:
      m_period_edit.LabelText("WPR Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  5:
      m_period_edit.LabelText("RSI Period");
      m_applied_price.Show();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  6:
      m_period_edit.LabelText("Momentum Period");
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  7:
      m_period_edit.LabelText("ADX Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  8:
      m_period_edit.LabelText("ADXW Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   case  9:
      m_period_edit.LabelText("Buffer Number");
      m_applied_price.Hide();
      m_custom_param.IsLocked(false);
      m_custom_path.IsLocked(false);
      break;
   default:
      m_period_edit.LabelText("Ind Period");
      m_applied_price.Hide();
      m_custom_param.IsLocked(true);
      m_custom_path.IsLocked(true);
      break;
   }
   m_period_edit.Update(true);
}

Jetzt sollte ein Kompilieren folgendes Ergebnis zeigen:

Abb. 3 Hinzufügen von Eingabefeldern für den nutzerdefinierten Indikator.

Der nächste Schritt ist die Ergänzung der Schaltfläche Ereignis des Signals hinzufügen. Wenn er gedrückt wird, setzen Sie die Auswahl und die Einstellungen des Indikators auf Standard.

      //--- Add Signal button click event
      if(lparam==m_add_signal.Id())
      {
         if(m_total_signals>4)
         {
            MessageBox("Maximum number of signals is 5","Signal Monitor");
            return;
         }
         m_set_window.OpenWindow();
         RebuildParameters(1);
         m_number_signal=-1;
         RebuildTimeframes();
         m_new_signal.LabelText("Add");
         m_new_signal.Update(true);
         m_indicator_type.SelectItem(1);
         m_indicator_type.GetButtonPointer().Update(true);
      }

Bevor wir neue Bedienelemente (Eingabefelder) an einen bestehenden Algorithmus anpassen, der eine Reihe von Signaleinstellungen speichert, sollten wir das System der Suchregeln für Handelssignale erweitern. Dies wird die Hinzufügung neuer Schnittstellenelemente bewirken. Es ist jedoch nicht sinnvoll, den Algorithmus zur Speicherung des Satzes jedes Mal zu ändern, wenn wir das System der Einstellungen erweitern. Eine logischere Lösung besteht darin, alle neuen Einstellungs- und Steuerelemente hinzuzufügen und dann die Methode zur Speicherung des Satzes von Parametern für die Signalsuche zu ändern.


Erweiterung des Systems der Suchregeln für Handelssignale

Gegenwärtig kann der Handelsmonitor Signale erzeugen, die auf Ungleichheit beruhen. Damit ist ein Zustand größer, kleiner oder gleich einer bestimmten Zahl gemeint. Eine solche Wahl spiegelt jedoch nicht immer genau die gewünschten Signale wider. Beispielsweise sind Oszillatorindikatoren manchmal für die Verwendung mit einem bestimmten Wertebereich besser geeignet. Genau das werden wir jetzt implementieren. Zunächst ist es notwendig, einen Schalter zwischen dem bisherigen und dem neuen Regelaufbausystem hinzuzufügen. Es sollte eine neue Dropdown-Liste mit zwei Regel-Setup-Typen hinzugefügt werden: Compare und Interval

Wir gehen zur Basisklasse CProgramm und fügen eine neue Variable hinzu, eine Instanz der Klasse CСombobox und erstellen die Methode, die das UI-Element implementiert:

CComboBox         m_rule_interval;

bool              CreateRuleInterval(const int x_gap,const int y_gap);

Die Methodenimplementierung sollte der Datei SetWindow.mqh hinzugefügt werden, da diese Dropdown-Liste zum Einstellungsfenster gehört.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleInterval(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_rule_interval.MainPointer(m_set_window);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Compare","Interval",
   };
//--- Set up properties before creation
   m_rule_interval.XSize(160);
   m_rule_interval.YSize(26);
   m_rule_interval.LabelYGap(4);
   m_rule_interval.ItemsTotal(2);
   m_rule_interval.Font(m_base_font);
   m_rule_interval.FontSize(m_base_font_size);
   m_rule_interval.BackColor(m_background);
   m_rule_interval.GetButtonPointer().Font(m_base_font);
   m_rule_interval.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_interval.GetButtonPointer().BackColor(clrWhite);
   m_rule_interval.GetButtonPointer().XGap(90);
   m_rule_interval.GetButtonPointer().XSize(80);
   m_rule_interval.GetListViewPointer().Font(m_base_font);
   m_rule_interval.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_interval.GetListViewPointer().ItemYSize(26);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_rule_interval.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_rule_interval.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_rule_interval.SelectItem(0);
//--- Create the control
   if(!m_rule_interval.CreateComboBox("Rule",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_rule_interval);
   return(true);
}

Die neue Intervallregel sollte eine untere und eine obere Grenze haben, fügen Sie also ein zusätzliches Feld für die Eingabe eines numerischen Wertes hinzu. Das vorherige Feld wird für den oberen Grenzwert verwendet, das neue Feld wird für den unteren verwendet. Es ist auch notwendig, die Möglichkeit vorzusehen, negative Werte für Indikatoren wie z.B. WPR anzugeben. In diesem Fall werden der obere und der untere Grenzwert vertauscht. Um zu vermeiden, dass für die Implementierung eines Eingabefeldes für eine untere Periode eine separate Methode erstellt werden muss, modifizieren Sie einfach die aktuelle Variable, die für das bestehende Eingabefeld verantwortlich ist, und die Methode CreateRule(). Die Variable wird dann zu einem Array:

CTextEdit         m_rule_value[2];

Fügen wir der Methode noch ein neues Argument hinzu, das eine Referenz auf die Instanz der Klasse CTextEdit erhält.

bool              CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Ändern wir die Methodenimplementierung entsprechend.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Save the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(80);
   text_edit.YSize(24);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.LabelColor(C'0,100,255');
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(999);
   text_edit.StepValue(0.1);
   text_edit.MinValue(-999);
   text_edit.SetDigits(3);
   text_edit.SpinEditMode(true); 
//--- Create the control
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
   text_edit.SetValue(string(5));
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

Ändern wir auch einige der Werte der bestehenden Methode CreateRule():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRule(const int x_gap,const int y_gap)
{
//--- Pass the object to the panel
   m_rule_type.MainPointer(m_set_window);
//--- Array of the item values in the list view
   string pattern_names[5]=
   {
      ">",">=","==","<","<="
   };
//--- Set up properties before creation
   m_rule_type.XSize(80);
   m_rule_type.YSize(26);
   m_rule_type.LabelYGap(4);
   m_rule_type.ItemsTotal(5);
   m_rule_type.Font(m_base_font);
   m_rule_type.FontSize(m_base_font_size);
   m_rule_type.BackColor(m_background);
   m_rule_type.GetButtonPointer().Font(m_base_font);
   m_rule_type.GetButtonPointer().FontSize(m_base_font_size);
   m_rule_type.GetButtonPointer().BackColor(clrWhite);
   m_rule_type.GetButtonPointer().XGap(1);
   m_rule_type.GetButtonPointer().XSize(80);
   m_rule_type.GetListViewPointer().Font(m_base_font);
   m_rule_type.GetListViewPointer().FontSize(m_base_font_size);
   m_rule_type.GetListViewPointer().ItemYSize(26);
//--- Save the item values in the combobox list view
   for(int i=0; i<5; i++)
      m_rule_type.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_rule_type.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_rule_type.SelectItem(0);
//--- Create the control
   if(!m_rule_type.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(1,m_rule_type);
   return(true);
}

Suchen wir nun den Abschnitt Condition Settings in der Methode CreateSetWindow(), und ändern den Code wie folgt:

//--- Condition settings
   if(!CreateRuleValue(m_rule_value[0],200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleValue(m_rule_value[1],300,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(200,22+10+5*(25+10)))
      return(false);
   if(!CreateRuleInterval(10,22+10+5*(25+10)))
      return(false);

Diese Änderung ermöglicht es Ihnen, die Position der vorhandenen Schnittstellenelemente neu zu konfigurieren und neue hinzuzufügen. Das Ergebnis sollte wie in Abb. 4 dargestellt sein. Allerdings funktioniert nichts wie bisher, wenn wir versuchen, den Modus der Regeln von Compare auf Interval umzuschalten. Lassen Sie uns das beheben.

Abb. 4 Hinzufügen der Modusauswahl für die Regeln der Signalsuche.

Öffnen Sie dazu die Methode OnEvent() und suchen Sie in der Dropdown-Liste den Abschnitt, der für ein Elementauswahl-Ereignis verantwortlich ist, und fügen Sie einen Code hinzu, der es ermöglicht, je nach ausgewähltem Modus korrekte Schnittstellenelemente anzuzeigen.

//--- Selecting an item in the combobox drop-down list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      ...
      //--- Rule type
      if(lparam==m_rule_interval.Id())
      {
         switch(m_rule_interval.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            m_rule_value[0].Hide();
            m_rule_type.Show();
            break;
         case  1:
            m_rule_value[0].Show();
            m_rule_type.Hide();
            break;
         default:
            break;
         }
      }
   }

Als Nächstes verschieben wir einige der Ereignisse im Zusammenhang mit dem Laden der Schnittstelle in einen separaten Abschnitt der Methode OnEvent(). Dazu erstellen wir das Ereignis der Vollendung der Schnittstellenerstellung und verschieben den Code von der Methode CreateGUI() zu diesem Ereignis. Der folgende Code wird in CreateGUI belassen:

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Loading the language
   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("Color Picker"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Der neue Abschnitt wird wie folgt aussehen:

// --- GUI creation completion
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      m_back_button.Hide();
      m_add_signal.Hide();
      m_signal_header.Hide();
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      for(int i=0; i<5; i++)
         m_signal_editor[i].Hide();
      m_rule_value[0].Hide();
   }

Achten Sie auf die neue Aktion beim Laden der Anwendung — Ausblenden des neu erstellten Feldes zur Eingabe der unteren Intervallgrenze.

Nachdem wir die neuen UI-Elemente und Parameter erstellt haben, können wir mit der Modifizierung des Ladealgorithmus und dem Speichern von Sätzen von Handelssignaleinstellungen fortfahren. Gehen wir zum Methodenrumpf SaveSignalSet() und passen ihn an die letzten Änderungen an.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveSignalSet(int index)
{
//---
   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>4)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше 5","Монитор сигналов");
      else
         MessageBox("Maximum number of signals is 5","Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- 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();
//--- Background color
   if(m_set_param[0].IsPressed())
      m_signal_set[index].back_color=m_color_button[1].CurrentColor();
   else
      m_signal_set[index].back_color=clrNONE;
//--- Border color
   if(m_set_param[1].IsPressed())
      m_signal_set[index].border_color=m_color_button[2].CurrentColor();
   else
      m_signal_set[index].border_color=clrNONE;
//--- Tooltip value
   m_signal_set[index].tooltip=m_set_param[2].IsPressed();
   if(m_signal_set[index].tooltip)
      StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
//--- Selected image
   m_signal_set[index].image=m_set_param[3].IsPressed();
   if(m_signal_set[index].image)
      m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//--- Selected timegrames
   int tf=0;
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
      {
         m_signal_set[index].timeframes[i]=true;
         StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
         tf++;
      }
      else
         m_signal_set[index].timeframes[i]=false;
   }
//---
   if(tf<1)
   {
      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 signal_"+string(index)+" has been successfully saved");
//---
   return(true);
}

Der obige Code enthält eine Vielzahl von Änderungen. Lassen Sie uns die wichtigsten Änderungen im Detail besprechen. Die erste ist die Prüfung, ob ein Standard- oder ein nutzerdefinierter Indikator ausgewählt ist. Wenn Sie Ihr eigenes Kennzeichen wählen, schreiben wir den Algorithmus der Speicherung des Pfades zum Kennzeichen und seiner Parameter sowie den Wert aus dem Periodeneingabefeld, aber wenn Sie Ihr eigenes Kennzeichen wählen, dient dieses Feld als Nummer des Indikatorpuffers.

Wir haben die Anzahl der gespeicherten Parameter geändert. Dementsprechend müssen wir die SIGNAL-Struktur ändern, über die alles in einer Binärdatei gespeichert wird. Hinzufügen der neuen Variablen:

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

Das Eingabefeld für den Schwellenwert ist jetzt für den Vergleich auf rule_value geändert, und es ist für die Ober- und Untergrenzen im Intervallmodus auf rule_value 1 und rule_value 2 geändert. Die Variablen custom_path und custom_val wurden hinzugefügt, um Daten über den benutzerdefinierten Indikatorpfad und seine Parameter zu speichern. Außerdem wurde die Methode zum Laden des Satzes von Handelssignalparametern aus einer Datei geändert: LoadSignalSet():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_signal_set[index]);
   FileReadStruct(h,m_signal_set[index]);
//--- Loading indicator type
   m_indicator_type.SelectItem(m_signal_set[index].ind_type);
   RebuildParameters(m_signal_set[index].ind_type);
   m_indicator_type.GetButtonPointer().Update(true);
   if(m_signal_set[index].ind_type!=9)
   {
      //--- Loading indicator period
      m_period_edit.SetValue((string)m_signal_set[index].ind_period);
      m_period_edit.GetTextBoxPointer().Update(true);
      //--- Loading applied price
      if(!m_applied_price.IsLocked())
      {
         m_applied_price.SelectItem(m_signal_set[index].app_price);
         m_applied_price.GetButtonPointer().Update(true);
      }
   }
   else
   {
      m_period_edit.SetValue((string)m_signal_set[index].ind_period);
      m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path));
      m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val));
      m_custom_path.GetTextBoxPointer().Update(true);
      m_custom_param.GetTextBoxPointer().Update(true);
   }
//--- Loading signal rule
   m_rule_interval.SelectItem(m_signal_set[index].rule_int);
   m_rule_interval.GetButtonPointer().Update(true);
   m_rule_type.SelectItem(m_signal_set[index].rule_type);
   m_rule_type.GetButtonPointer().Update(true);
   m_rule_value[0].SetValue((string)m_signal_set[index].rule_value1);
   m_rule_value[0].GetTextBoxPointer().Update(true);
   m_rule_value[1].SetValue((string)m_signal_set[index].rule_value2);
   m_rule_value[1].GetTextBoxPointer().Update(true);
//--- Loading a text label
   if(m_signal_set[index].label_type==0)
   {
      m_label_button[0].IsPressed(true);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(false);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(true);
   }
   else
   {
      m_label_button[0].IsPressed(false);
      m_label_button[0].Update(true);
      m_label_button[1].IsPressed(true);
      m_label_button[1].Update(true);
      m_text_box.IsLocked(false);
      m_text_box.ClearTextBox();
      m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value));
      m_text_box.Update(true);
   }
//--- Loading the color of the text label
   m_color_button[0].CurrentColor(m_signal_set[index].label_color);
   m_color_button[0].Update(true);
//--- Loading the background color
   if(m_signal_set[index].back_color==clrNONE)
   {
      m_set_param[0].IsPressed(false);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(true);
      m_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[0].IsPressed(true);
      m_set_param[0].Update(true);
      m_color_button[1].IsLocked(false);
      m_color_button[1].CurrentColor(m_signal_set[index].back_color);
      m_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_signal_set[index].border_color==clrNONE)
   {
      m_set_param[1].IsPressed(false);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(true);
      m_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_set_param[1].IsPressed(true);
      m_set_param[1].Update(true);
      m_color_button[2].IsLocked(false);
      m_color_button[2].CurrentColor(m_signal_set[index].border_color);
      m_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_signal_set[index].tooltip)
   {
      m_set_param[2].IsPressed(false);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(true);
      m_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_tooltip_text.IsLocked(false);
      m_tooltip_text.ClearTextBox();
      m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text));
      m_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_signal_set[index].image)
   {
      m_set_param[3].IsPressed(false);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(true);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_set_param[3].IsPressed(true);
      m_set_param[3].Update(true);
      m_pictures_slider.IsLocked(false);
      m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index);
      m_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//--- Loading selected timeframes
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked())
      {
         m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]);
         m_tf_button[i].Update(true);
      }
   }
//---
   FileClose(h);
   return(true);
}

Es ist eine Prüfung implementiert, ob ein Standardindikator aus einer Datei ausgewählt wurde oder ein nutzerdefinierter Indikator verwendet wird. Entsprechend werden die erforderlichen Daten zur weiteren Bearbeitung in die Oberfläche des Einstellungsfensters geladen.

Das Speichern und Laden eines Satzes von Handelssignalparametern ist vorbereitet. Verfeinern wir nun den Signalsuchalgorithmus. Öffnen Sie die Methode GetSignal() und suchen Sie den Abschnitt Check for conditions. Ersetzen Sie den Abschnitt wie folgt:

//--- Check the condition
   int s=0;
   if(signal_set.rule_int==0)
   {
      double r_value=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      switch(signal_set.rule_type)
      {
      case  0:
         if(c_value>r_value)
            s=1;
         break;
      case  1:
         if(c_value>=r_value)
            s=1;
         break;
      case  2:
         if(c_value==r_value)
            s=1;
         break;
      case  3:
         if(c_value<r_value)
            s=1;
         break;
      case  4:
         if(c_value<=r_value)
            s=1;
         break;
      default:
         s=0;
         break;
      }
   }
   else if(signal_set.rule_int==1)
   {
      double r_value_min=signal_set.rule_value1;
      double r_value_max=signal_set.rule_value2;
      double c_value=val[0];
      m_ind_value=c_value;
      if(c_value>=r_value_min && c_value<=r_value_max)
         s=1;
   }

Nehmen Sie auch die hinzugefügten Indikatoren in den Abschnitt Abrufen des Handles des ausgewählten Indikators:

//--- Get the handle of the selected indicator
   string str[],name;
   double arr[];
   switch(signal_set.ind_type)
   {
   case  0:
      h=iATR(sy,tf,signal_set.ind_period);
      break;
   case  1:
      h=iCCI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  2:
      h=iDeMarker(sy,tf,signal_set.ind_period);
      break;
   case  3:
      h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK);
      break;
   case  4:
      h=iWPR(sy,tf,signal_set.ind_period);
      break;
   case  5:
      h=iRSI(sy,tf,signal_set.ind_period,app_price);
      break;
   case  6:
      h=iMomentum(sy,tf,signal_set.ind_period,app_price);
      break;
   case  7:
      h=iADX(sy,tf,signal_set.ind_period);
      break;
   case  8:
      h=iADXWilder(sy,tf,signal_set.ind_period);
      break;
   case  9:
      StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",0),str);
      ArrayResize(arr,ArraySize(str));
      for(int i=0; i<ArraySize(str); i++)
         arr[i]=StringToDouble(str[i]);
      name=m_custom_path.GetValue();
      h=GetCustomValue(tf,name,arr);
      break;
   default:
      break;
   }

Dieser Block enthält nun die Prüfung für den Suchmodus, Comparison oder Interval. Die Prüfung der Bedingungen wird entsprechend angewendet.


Umwandlung der Symbolliste in eine Tabelle

Wenn es nicht viele Symbole auf einem Handelskonto gibt, kann die Möglichkeit, sie durch den Namen mit einem Kontrollkästchen daneben implementiert auszuwählen, völlig ausreichend sein. Wenn man jedoch mit Hunderten von Symbolen arbeitet, nimmt die Höhe des Anwendungsfensters stark zu (da es entsprechend der Anzahl der Zeilen mit Symbolen skaliert wird). Aus diesem Grund wurde diese Ansicht durch eine tabellarische Form ersetzt. Außerdem werden bei zu vielen Symbolen einige von ihnen ausgeblendet und eine Bildlaufleiste auf der rechten Seite hinzugefügt. Wir benötigen jedoch weiterhin Kontrollkästchen für die Auswahl von Arbeitszeitrahmen. Daher müssen wir mehrere Probleme lösen:

Lassen Sie uns zunächst die Anzeige der alten Kontrollkästchen entfernen. Dazu blenden wir sie im Ereignis GUI-Erstellung abschließen aus. Nun können wir wissen, dass die Anzahl der Kontrollkästchen konstant ist: 21, was nach Möglichkeit der Gesamtzahl der Zeitrahmen im Terminal entspricht. Wir wandeln daher das dynamische Array m_checkbox[] in ein statisches Array mit einer Größe von 21 um.

      //--- Hide timeframe checkboxes
      for(int i=0; i<21; i++)
         m_checkbox[i].Hide();

Auch die Methode zur Erstellung von Kontrollkästchen muss aufgrund ihres eindeutigen Zwecks angepasst werden. Wir gehen zum Code der Methode CreateStepWindow() und ersetzen den Abschnitt Checkboxes wie folgt:

//--- Checkboxes
   int k=0;
   string timeframe_names[21]=
   {
      "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30",
      "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
   };
   for(int j=0; j<=3; j++)
   {
      for(int i=0; i<7; i++)
      {
         if(k<21)
            if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,timeframe_names[k]))
               return(false);
         k++;
      }
   }

Entfernen wir auch das Fenster zur Berechnung der Fensterhöhe (es war notwendig, um die Höhe entsprechend der Anzahl der Symbole in der Market Watch zu berechnen).

m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);

Das Fenster wird statisch:

m_step_window.YSize(500);

Jetzt müssen wir das Grundobjekt einer Tabelle erstellen, die dann mit Daten aus der Market Watch gefüllt wird. Wir erstellen die Klasseninstanz CTable und die Tabellenimplementierungsmethode.

//--- Rendered table
   CTable            m_table;

   bool              CreateTable(const int x_gap,const int y_gap);

Implementation des Hauptfensters in der Datei StepWindow.mqh:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
{
#define COLUMNS1_TOTAL 7
#define ROWS1_TOTAL int(MathCeil(m_all_symbols/7))
//--- Save the pointer to the main control
   m_table.MainPointer(m_step_window);
//--- Array of column widths
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,80);
//--- Array of text offset along the X axis in the columns
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,25);
//--- Array of text alignment in columns
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_LEFT);
//--- Array of column image offsets along the X axis
   int image_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_x_offset,5);
//--- Array of column image offsets along the Y axis
   int image_y_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(image_y_offset,4);
//--- Properties
   m_table.XSize(560);
   m_table.YSize(190);
   m_table.Font(m_base_font);
   m_table.FontSize(m_base_font_size);
   m_table.CellYSize(20);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ImageXOffset(image_x_offset);
   m_table.ImageYOffset(image_y_offset);
   m_table.LabelXGap(5);
   m_table.LabelYGap(4);
   m_table.IconXGap(7);
   m_table.IconYGap(4);
   m_table.MinColumnWidth(0);
   m_table.LightsHover(true);
   m_table.SelectableRow(false);
   m_table.IsWithoutDeselect(false);
   m_table.ColumnResizeMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoXResizeRightOffset(10);
   m_table.AutoYResizeMode(true);
   m_table.AutoYResizeBottomOffset(50);
//--- Populate the table with data
   InitializingTable();
//--- Create the control
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
}

Bevor diese Methode zur Erstellung einer Tabelle verwendet wird, sollten folgende Daten eingetragen werden: die Liste aller Symbole bildet die Market Watch für das aktuelle Handelskonto. Dies wird durch die Methode InitializingTable() durchgeführt: fügen Sie sie in den privaten Bereich der Basisklasse ein:

//+------------------------------------------------------------------+
//| Initialize the table                                             |
//+------------------------------------------------------------------+
void CProgram::InitializingTable(void)
{
//--- Array of icons 1
   string image_array1[2]=
   {
      "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp",
      "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp"
   };
//---
   int k=0;
   for(int c=0; c<COLUMNS1_TOTAL; c++)
   {
      //---
      for(int r=0; r<ROWS1_TOTAL; r++)
      {
         if(k<m_all_symbols)
         {
            //--- Set the cell type to Checkbox
            m_table.CellType(c,r,CELL_CHECKBOX);
            m_table.SetImages(c,r,image_array1);
            //--- Set the text
            m_table.SetValue(c,r,SymbolName(k,false));
         }
         k++;
      }
   }
}

Verwenden wir nun die obigen Vorbereitungen im Methodenkörper von CreateStepWindow(), um die Tabelle zu erstellen und zu füllen. Als Ergebnis erhalten wir eine Liste aller Symbole des Market Watch (Abb.5), die ebenfalls auf dem Checkbox-Auswahltyp basiert, nun aber in tabellarischer Form dargestellt wird.

Abb. 5 Ergebnis der Konvertierung der Liste der Symbole in eine tabellarische Form.

Der nächste Schritt besteht darin, die neu erstellte Tabelle mit den Interaktionen zu verknüpfen, die für den vorherigen Satz von Kontrollkästchen verfügbar waren. Die folgenden Möglichkeiten sollten vorgesehen werden:

Um die erste Möglichkeit zu implementieren, ersetzen Sie Abschnitte mit Vorlagennamen in OnEvent() durch den folgenden Code:

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,1);
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Majors
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"};
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<4; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //--- Crosses
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[20]=
            {
               "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY",
               "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF"
            };
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            //---
            k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                  {
                     for(int j=0; j<20; j++)
                     {
                        if(m_table.GetValue(c,r)==pairs[j])
                           m_table.ChangeImage(c,r,1);
                     }
                  }
                  k++;
               }
            }
            m_table.Update(true);
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Clear the selection
            int k=0;
            for(int c=0; c<7; c++)
            {
               //---
               for(int r=0; r<MathCeil(m_all_symbols/7); r++)
               {
                  if(k<m_all_symbols)
                     m_table.ChangeImage(c,r,0);
                  k++;
               }
            }
            m_table.Update(true);
         }

Das Speichern und Laden ausgewählter Symbolsätze wurde mit Hilfe von Methoden durchgeführt. Wir verwendeten die Methoden SaveSymbolSet() und LoadSymbolSet(), um die ausgewählten Symbolsätze zu speichern bzw. zu laden. Hier müssen wir den Codeteil ändern, in dem Daten aus einem Kontrollkästchen entnommen werden, da die Daten aus einer neu erstellten Tabelle entnommen werden sollen. Dementsprechend sollten die Daten in dieselbe Tabelle geladen werden.

//+------------------------------------------------------------------+
//| Save template to a file                                          |
//+------------------------------------------------------------------+
bool CProgram::SaveSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для записи","Монитор сигналов");
      else
         MessageBox("Choose a name for the template to save","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Mo
nitor");
      return(false);
   }
   else
      MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor");
//--- Save symbol selection
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
            m_save.tf[k]=m_table.SelectedImageIndex(c,r)>0?true:false;
         k++;
      }
   }
//---
   FileWriteStruct(h,m_save);
   FileClose(h);
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Load data to a panel                                             |
//+------------------------------------------------------------------+
bool CProgram::LoadSymbolSet(string file_name)
{
   if(file_name=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Выберите имя шаблона для загрузки","Монитор сигналов");
      else
         MessageBox("Choose a name for the template to load","Signal Monitor");
      return(false);
   }
   int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration "+file_name+" not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_save);
   FileReadStruct(h,m_save);
//--- Load symbol selection
   int k=0;
   for(int c=0; c<7; c++)
   {
      //---
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(k<m_all_symbols)
         {
            if(m_save.tf[k])
               m_table.ChangeImage(c,r,1);
            else
               m_table.ChangeImage(c,r,0);
         }
         k++;
      }
   }
   m_table.Update(true);
//---
   FileClose(h);
//---
   return(true);
}

Markieren wir nun den Block, in dem Daten gesammelt und in einer Struktur gespeichert werden, die in einer Datei gespeichert wird. Außerdem werden hier Daten aus einer Datei in eine Struktur geladen und Daten aus dieser in die Tabelle eingefügt.

Das visuelle Ergebnis aller Änderungen und Ergänzungen ist in Abbildung 6 dargestellt, aber der Hauptzweck bestand darin, eine komfortable Bedienung mit einer großen Anzahl von Symbolen zu ermöglichen, die nicht in das Fenster passten.

Abb.6 Das Ergebnis der Addition einer Tabelle und ihre Interaktion mit den UI-Elementen.

Außerdem müssen wir die Methoden der Übergänge zwischen Schritt 1 und Schritt 2 bearbeiten, da wir die Art und Weise, wie wir Informationen über ausgewählte Symbole erhalten, geändert haben. Übergänge zwischen Konfigurationsschritten werden über zwei Methoden durchgeführt, die geändert werden müssen. Die Methode Zu_Step1() sollte wie folgt geändert werden: Wenn wir von Schritt 2 zu Schritt 1 springen, sollte die Möglichkeit, Zeitrahmen auszuwählen, ausgeblendet und die Tabelle angezeigt werden.

//+------------------------------------------------------------------+
//| Go to Step 1                                                     |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//--- Change header
   m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols");
   m_step_window.Update(true);
//--- Hide the Back button
   m_back_button.Hide();
//--- Show the table
   m_table.Show();
//--- Hide timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Hide();
   string names[3]= {"All","Majors","Crosses"};
//--- Change names of selection buttons
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].IsPressed(false);
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].Update(true);
   }
//--- Show block for working with templates
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
//--- Set the current setup step
   m_current_step=1;
}

Blenden Sie in der Methode To_Step2() die Tabelle aus, Zeitrahmenauswahl anzeigen und merken Sie sich die im ersten Schritt ausgewählten Symbole. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Check whether at least one symbol is selected
   int cnt=0;
//---
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            cnt++;
      }
   }
//---
   if(cnt<1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не выбран ни один символ!","Внимание");
      else
         MessageBox("No symbols selected!","Warning");
      return;
   }
//--- Hide the table
   m_table.Hide();
//--- Display timeframes
   for(int i=0; i<21; i++)
      m_checkbox[i].Show();
//--- Count the number of selected symbols
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Remember the selected symbols in the array
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
         {
            m_symbols[cnt]=m_table.GetValue(c,r);
            cnt++;
         }
      }
   }
//--- Set selected symbols in Market Watch
   for(int c=0; c<7; c++)
   {
      for(int r=0; r<MathCeil(m_all_symbols/7); r++)
      {
         if(m_table.SelectedImageIndex(c,r)>0)
            SymbolSelect(m_table.GetValue(c,r),true);
         else
            SymbolSelect(m_table.GetValue(c,r),false);
      }
   }
//---
   if(m_current_step==3)
   {
      m_add_signal.Hide();
      m_signal_header.Hide();
      m_next_button.LabelText("Next");
      m_next_button.Update(true);
      for(int i=0; i<5; i++)
         m_signal_editor[i].Hide();
      ClearSaves();
   }
//--- Change header
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
   string names[3]= {"All","Junior","Senior"};
//--- Change names of selection buttons
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].IsPressed(false);
      if(m_current_step==3)
         m_currency_set[i].Show();
      m_currency_set[i].Update(true);
   }
//--- Hide block for working with templates
   m_text_edit.Hide();
   m_load_button.Hide();
   m_save_button.Hide();
//--- Show Back button
   m_back_button.Show();
//---
   m_current_step=2;
}

Jetzt müssen wir die Interaktion der Schaltflächen zur Auswahl der voreingestellten Zeitrahmensätze mit der Liste der Kontrollkästchen anpassen. Da die Liste jetzt konstant ist, sollten entsprechende Änderungen im Code vorgenommen werden:

//--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(true);
               m_checkbox[i].Update(true);
            }
         }
         //--- Junior Timeframes
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[11]=
            {
               "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30"
            };
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<11; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //--- Senior Timeframes
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[10]=
            {
               "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
            };
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<10; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //---
         if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
               (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
               (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
           )
         {
            //--- Clear the selection
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
         }


Schnelle Bearbeitung der Suchregeln im Monitor

Während der Signalüberwachung muss der Nutzer möglicherweise die Bedingungen eines zuvor erstellten Handelssignals ändern. Gegenwärtig kann dies durch einen Neustart der Anwendung und eine Neukonfiguration jedes Signals des Monitors zusätzlich zum erforderlichen Signal erfolgen. Diese Lösung ist umständlich. Lassen Sie uns daher die Möglichkeit anbieten, fertige Handelssignale vom Monitor selbst aus zu bearbeiten. Fügen wir der Oberfläche des Monitors eine Schaltfläche hinzu, die es ermöglicht, ein kleines Dialogfeld zu öffnen, wie in Abbildung 7 dargestellt. Das Kästchen wird eine Liste aller erstellten Handelssignale enthalten. Ein Klick auf ein Signal öffnet es zur Bearbeitung.

Abb. 7 Bearbeiten eines früher erstellten Signals vom Monitor aus.

Fahren wir mit der Implementierung fort. Um die Schaltfläche anzuzeigen, die das Fenster mit der Liste der Handelssignale öffnet, fügen Sie die folgende Eigenschaft im Methodenrumpf CreateStepWindow() hinzu:

m_step_window.TooltipsButtonIsUsed(true);

 Und deaktivieren Sie sie dann im Ereignis GGUI creation completion - so wird die Schaltfläche beim ersten Schritt der Anwendungseinrichtung nicht angezeigt und wird erst angezeigt, nachdem alle Signale erstellt und der Monitor gestartet wurde:

// --- GUI creation completion
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      ...
      m_step_window.GetTooltipButtonPointer().Hide();
   }

Aktivieren Sie sie beim Laden des Monitors.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AutoResize(const int x_size,const int y_size)
{
   ...
   m_step_window.GetTooltipButtonPointer().Show();
}

Erstellen wir nun ein neues Dialogfeld, in dem die Liste der erstellten Signale angezeigt wird. Wir erzeugen eine Variable in der Klasse CWindow und die Methode CreateFastEdit(), die die Fenstererzeugung implementiert, sowie die Methode CreateFastEditor() zur Erzeugung von Schaltflächen (die Signalbearbeitung wird durch Klicken auf diese Schaltflächen durchgeführt).

   CWindow           m_fast_edit;

   bool              CreateFastEdit(const string caption_text);
   bool              CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap);

Implementation dieser Methode

//+------------------------------------------------------------------+
//| Creates a window for creating and editing trading signals        |
//+------------------------------------------------------------------+
bool CProgram::CreateFastEdit(const string caption_text)
{
//--- Add the window pointer to the window array
   CWndContainer::AddWindow(m_fast_edit);
//--- Properties
   m_fast_edit.XSize(180);
   m_fast_edit.YSize(280);
//--- Coordinates
   int x=m_step_window.XGap()+m_step_window.XSize()+10;
   int y=m_step_window.YGap();
//---
   m_fast_edit.CaptionHeight(22);
   m_fast_edit.IsMovable(true);
   m_fast_edit.CaptionColor(m_caption);
   m_fast_edit.CaptionColorLocked(m_caption);
   m_fast_edit.CaptionColorHover(m_caption);
   m_fast_edit.BackColor(m_background);
   m_fast_edit.FontSize(m_base_font_size);
   m_fast_edit.Font(m_base_font);
   m_fast_edit.WindowType(W_DIALOG);
//--- Creating the form
   if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   for(int i=0; i<5; i++)
   {
      if(!CreateFastEditor(m_fast_editor[i],"Signal_"+string(i),10,40*i+40))
         return(false);
   }
   return(true);
}
//+------------------------------------------------------------------+
//| Creates a button with an image                                   |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"
bool CProgram::CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Save the window pointer
   button.MainPointer(m_fast_edit);
//--- Set up properties before creation
   button.XSize(110);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.IconXGap(3);
   button.IconYGap(7);
   button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp");
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create the control
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(3,button);
   return(true);
}

Aufruf der Methode CreateFastEdit() in der Methode CreateGUI().

//+------------------------------------------------------------------+
//| Creates the graphical interface of the program                   |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//---
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow("Color Picker"))
      return(false);
//--- Creating a quick edit form
   if(!CreateFastEdit("Fast Signal Editor"))
      return(false);
//--- Finishing the creation of GUI
   CWndEvents::CompletedGUI();
   return(true);
}

Nun sollte ein Klick auf die Schaltfläche Einstellungen im Monitor die Dialogbox mit den Signalen öffnen. Fügen Sie dazu den folgenden Code in den Abschnitt Klick auf die Schaltfläche Ereignis der Methode Ereignis() ein:

      //--- OPEN THE SETTING WINDOW
      if(lparam==m_step_window.GetTooltipButtonPointer().Id())
      {
         //--- Coordinates
         int x=m_step_window.X()+m_step_window.XSize()+10;
         int y=m_step_window.Y();
         m_fast_edit.X(x);
         m_fast_edit.Y(y);
         m_fast_edit.OpenWindow();
      }

Das folgende Ergebnis erhalten Sie, wenn Sie das Projekt jetzt kompilieren:

Abb. 8 Hinzufügen eines Fensters zur schnellen Bearbeitung von Handelssignalen.

Jetzt enthält das Kästchen alle Schaltflächen zur Signalbearbeitung, aber die Idee ist, nur die erstellten Signale anzuzeigen. Fügen wir also eine Prüfung für die aktuelle Anzahl der verfügbaren Signale hinzu. Dies kann durch das Ereignis Dialogboxöffnung erfolgen:

//--- Opening a dialog window
   if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
   {
      if(m_current_step<4)
         return;
      for(int i=0; i<5; i++)
      {
         if(!FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
            m_fast_editor[i].Hide();
      }
   }

Hier wird geprüft, ob Dateien mit Handelssignal vorhanden sind. Dabei werden nur die früher erzeugten Signale angezeigt. Nun sollte ein Klick auf die Schaltfläche mit dem erzeugten Signal das Bearbeitungsfenster für dieses Signal öffnen. Dies geschieht im Abschnitt Klick auf die Schaltfläche Ereignis.

      //--- Trading signal editing
      for(int i=0; i<5; i++)
      {
         if(lparam==m_fast_editor[i].Id())
         {
            m_fast_edit.CloseDialogBox();
            LoadSignalSet(i);
            m_new_signal.LabelText("Save");
            m_new_signal.Update(true);
            RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex());
            m_set_window.OpenWindow();
            m_number_signal=i;
         }
      }

Ein Klick auf eines der Signale im Schnellbearbeitungsfenster öffnet das Einstellungsfenster und lädt die zuvor gespeicherten Daten dieses Handelssignals. Sobald die erforderlichen Daten geändert wurden, sollten die neuen Einstellungen in eine Datei geschrieben werden. In diesem Fall brauchen wir nicht das gesamte Verfahren zur Einrichtung des Monitors abzuschließen.

Lokalisierung der Anwendung

Um die Lokalisierungsaufgabe zu lösen, ist es notwendig, alle zu übersetzenden GUI-Elemente zu bestimmen, wobei einige von ihnen so belassen werden sollten, wie sie sind, da ihre Namen allgemein anerkannt sind. Wir werden einen einfachen Mechanismus verwenden: Wir werden ein String-Array mit Daten erstellen, die je nach gewählter Sprache für die Ersetzung in Oberflächenelementen verwendet werden. Wir werden zwei Sprachen haben: Russisch und Englisch. Zuerst erstellen wir in der Datei SignalMonitor.mq5 eine Enumeration, die es uns ermöglicht, die gewünschte UI-Sprache beim Start auszuwählen. Die Namen für einige der Elemente werden nach englischen Standards festgelegt.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum UPDATE
{
   MINUTE,        // 1 minute
   MINUTE_15,     // 15 minutes
   MINUTE_30,     // 30 minutes
   HOUR,          // 1 hour
   HOUR_4         // 4 hour
};
enum LANG
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};
//+------------------------------------------------------------------+
//| Expert Advisor input parameters                                  |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Base Font
input color                Caption           =  C'0,130,225';        // Caption Color
input color                Background        =  clrWhiteSmoke;       // Back color
input LANG                 Language          =  ENGLISH;             // Interface language
input UPDATE               Update            =  MINUTE;              // Update interval

Um Informationen über die ausgewählte Sprache an die Schnittstelle zu übergeben, erstellen Sie eine Variable im öffentlichen Bereich der Basisklasse CProgram.

   //---
   int               m_language;

Der Index der ausgewählten Sprache wird der Variable während der Anwendungsinitialisierung zugewiesen.

   program.m_language=Language;

Erstellen wir dann ein Array im 'private' Teil der Basisklasse, das als Empfänger von Daten dient, die in der Schnittstelle entsprechend der gewählten Sprache ersetzt werden sollen. Erstellen wir auch eine Methode, die Daten in die Schnittstelle lädt.

   string            m_lang[];

   void              ChangeLanguage(void);

Implementieren wir nun die deklarierte Methode in der Datei Program.mqh und setzen die Sprachwerte auf die entsprechenden Felder der einzelnen GUI-Elemente.

//+------------------------------------------------------------------+
//| Changing the interface language                                  |
//+------------------------------------------------------------------+
void CProgram::ChangeLanguage(void)
{
//---
#define ITEMS 40
   ArrayResize(m_lang,ITEMS);
   string rus[ITEMS]=
   {
      "Монитор Сигналов Шаг 1: Выбор Символов","Все","Мажоры","Кроссы",
      "Назад","Далее","Загрузка(L)","Сохранить(S)","Имя шаблона","Монитор сигналов Шаг 2: Выбор таймфреймов",
      "Все","Младшие","Старшие",
      "Монитор Сигналов Шаг 3: Создание торговых сигналов","Создать","Добавить сигнал","Список сигналов",
      "Редактор торговых сигналов","Тип индикатора","1.Настройки индикатора","Примен. цена",
      "Введите путь индикатора","Введите параметры индикатора через запятую",
      "2.Настройка сигнала","Правило","Метка","Значение","Текст","Цвет метки","Фон","Кант","Подсказка",
      "Изображение","Таймфреймы","Добавить","Отмена","Монитор торговых сигналов","Номер буфера","Сохранить"
   };
   string eng[ITEMS]=
   {
      "Signal Monitor Step 1: Choose Symbols","ALL","Major","Crosses",
      "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step 2: Choose Timeframes",
      "ALL","Junior","Senior",
      "Signal Monitor Step 3: Creating Trading Signals","Create","Add Signal","Signal List",
      "Signal Monitor Edit Signal","Indicator Type","1.Indicator Settings","Applied Price",
      "Enter the indicator path","Enter indicator parameters separated by commas",
      "2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip",
      "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save"
   };
//--- Russian
   if(m_language==0)
      ArrayCopy(m_lang,rus);
//--- English
   else
      ArrayCopy(m_lang,eng);
}

So haben wir zusätzlich die russische Sprache implementiert (Abb.9). Sie können auf ähnliche Weise Ihre bevorzugte Sprache hinzufügen

Abb. 9 Ergebnis der GUI-Lokalisierung.


Zusätzliche Eigenschaften

Einige zusätzliche Funktionen werden den visuellen Teil des Monitors verbessern und es wird die Möglichkeit bieten, schnell zu dem Symboldiagramm zu wechseln, in dem ein Signal aufgetaucht ist. Der visuelle Teil ist die Erweiterung des Signalblocks, da die derzeit verwendete Form klein erscheint. Finden Sie die Methode CreateSignalButton() und vergrößern Sie die Größe der Signalblöcke, und passen Sie die Position der Elemente innerhalb dieser Blöcke und die Anordnung der Blöcke relativ zueinander in der Methode To_Monitor() an.

   button.XSize(60);
   button.YSize(30);
   button.IconXGap(2);
   button.IconYGap(11);
   button.LabelXGap(19);
   button.LabelYGap(10);

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

Diese Monitor-Implementierung ist für die Nachverfolgung viel bequemer.

Abb. 10 Größenänderung von Signalblöcken und Anpassung der Monitorschnittstelle.

Lassen Sie uns nun das Öffnen eines Diagramms für das Symbol und den Zeitrahmen, in dem das Signal gefunden wurde, implementieren. Das Diagramm sollte durch einen Klick auf den entsprechenden Block geöffnet werden. Fügen wir in der Methode OnEvent(), im Abschnitt Klick auf die Schaltfläche Ereignis folgendes hinzu (weil Signalblöcke Schaltflächen sind):

      //--- CLICKING ON THE SIGNAL BLOCK
      for(int i=0; i<ArraySize(m_signal_button); i++)
      {
         if(lparam==m_signal_button[i].Id())
            ChartOpen(GetSymbol(i),GetTimeframe(i));
      }

Ist alles ganz einfach. Zu diesem Zeitpunkt ist die derzeitige Entwicklungsphase abgeschlossen. Im nächsten Teil werden wir das Signalsuchsystem weiter verbessern, das Konzept eines zusammengesetzten Signals einführen und die Möglichkeiten der Monitorsteuerung erweitern.


Schlussfolgerung

Das unten angehängte Archiv enthält alle beschriebenen Dateien in Ordnern. Für einen korrekten Betrieb sollten Sie den Ordner MQL5 im Stammverzeichnis des Terminals speichern. Um das Stammverzeichnis des Terminals zu öffnen, in dem sich der Ordner MQL5 befindet, drücken Sie die Tastenkombination Strg+Umschalt+D im Terminal MetaTrader 5 oder verwenden Sie das Kontextmenü wie in Abb. 11 unten dargestellt.


Abb. 11. Öffnen des Ordners MQL5 im Stammordner von MetaTrader 5