Die Handelssignale mehrerer Währungen überwachen (Teil 5): Signalkombinationen

24 August 2020, 11:18
Alexander Fedosov
0
485

Inhalt

Einführung

Im vorherigen Teil der Artikelserie, der sich mit der Erstellung eines Monitors für Handelssignale in mehreren Währungen befasst, haben wir die Möglichkeit eingeführt, nutzerdefinierte Indikatoren zu verwenden, und das System der logischen Regeln erweitert, die es ermöglichen, Daten zu filtern. Viele Händler und Entwickler verwenden jedoch Handelssysteme, die auf mehreren Indikatoren basieren. Jeder der Indikatortypen, einschließlich Trend, Seitwärtsmarkt und Oszillatoren, hat auf bestimmten Märkten seine Nachteile.

Beispielsweise erzeugen Trendindikatoren bei einer anhaltenden Seitwärtsbewegung oft viele falsche Signale. Daher müssen sie durch einige andere Instrumente bestätigt werden. Dies kann erreicht werden, indem zusätzlich zum Hauptindikator weitere Indikatoren hinzugefügt werden, die als Filter für die vom Hauptindikator erzeugten Signale dienen. Optional können Händler ein Handelssystem erstellen, das auf einer Reihe von Indikatoren basiert, die die gleichen Marktzustände anzeigen. In diesem Fall kann das Markteintrittssignal durch gleichzeitige Bestätigungen von mehreren Indikatoren erzeugt werden.

Daher ist der nächste logische Schritt in unserer Indikatorenentwicklung die Einführung von zusammengesetzten Signalen, die aus verfügbaren einfachen Signalen bestehen. Wir werden also mit dem Konzept eines zusammengesetzten Signals arbeiten. Wir werden ein Projekt aus dem vorherigen Teil als Grundlage verwenden. Das Projekt ist am Ende des vorhergehenden Artikels verfügbar.


Signalkombination

Lassen Sie uns zunächst das Konzept eines "zusammengesetzten Signals", einer Signalkombination definieren, das in unserem Projekt verwendet werden soll. In früheren Versionen verwendeten wir einfache Signale, die durch die Anwendung spezifischer Bedingungen auf die empfangene Quelle erzeugt wurden, die durch verschiedene Indikatoren, wie RSI, WPR und CCI, dargestellt werden konnten. Außerdem wurde die Möglichkeit eingeführt, nutzerdefinierte Indikatoren zu verwenden.

Eine Signalkombination ist ein Signal, das aus zwei oder mehr einfachen Signalen besteht, die durch logische UND/ODER-Operatoren miteinander verbunden sind.

Das zusammengesetzte Signal enthält also mehrere zuvor erzeugte einfache Signale, die durch logische Operatoren miteinander interagieren. Es wird möglich sein, ein komplexes Signal zu erzeugen, das die Bedingung für das gleichzeitige Vorhandensein von zwei oder drei einfachen Signalen in einer bestimmten Zeitspanne enthält. Das Handelssystem wird also über ein Hauptsignal und einen Filter verfügen. Der logische ODER-Operator wird es uns ermöglichen, nach einem Handelssignal in mehreren Richtungen gleichzeitig zu suchen und so einen größeren Bereich bei der Analyse der aktuellen Marktzustände abzudecken.

Zur besseren Erklärung finden Sie hier einen Plan zur Aktualisierung und Erweiterung unserer Anwendungen. Zunächst einmal müssen wir die Anwendungsschnittstelle aktualisieren und neue Steuerelemente hinzufügen.


Abb. 1. Hinzufügen von Schnittstellen-Elementen

Der linke Teil von Abbildung 1 ist Ihnen bereits bekannt. Es handelt sich um eine Schaltfläche zum Hinzufügen eines Signals, gefolgt von den bereits hinzugefügten einfachen Signalen. Nun ist es notwendig, eine Schaltfläche zum Hinzufügen einer Signalkombination (1), eine Liste der hinzugefügten Signalkombinationen (2) und eine dritte Gruppe von Schaltflächen (3) zu erstellen, die es ermöglichen, das Verwendungsflag für einfache Signale zu setzen:

  • S(Simple) — ein einfaches Signal. Es wird als ein unabhängiges Signal im Monitor verwendet und kann in einem zusammengesetzten Signal nicht ausgewählt werden.
  • C(Composite) — ein einfaches Signal, das nur als Teil einer Signalkombination verwendet werden kann. Es kann in einem Monitor nicht als unabhängiges Signal verwendet werden.
  • B(Both) — beide Anwendungstypen. Ein solches Signal kann als unabhängiges Signal im Monitor gesucht und als Teil einer Signalkombination verwendet werden.

Ein Schema zur Erzeugung einer Signalkombination ist unten in Abbildung 2 dargestellt. Elemente mit den Namen Simple 1 und Simple 2 zeigen eine Liste bereits erzeugter einfacher Signale, die bei der Erzeugung eines komplexen Signals verwendet werden können. Was die Signalverwendungsattribute betrifft, so sollten die in Abbildung 2 gezeigten Signale ein Attribut "C" oder "B" haben, da sie sonst nicht in der Liste angezeigt würden.

Abb.2 Das Schema des Fensters zur Erzeugung und Bearbeitung der Signalkombination

Darauf folgt ein Abschnitt namens Rule (Regel), in dem die Signalkombination erzeugt wird. Ein Signal aus den obigen Listen kann zu jedem der drei Positionen (slot) hinzugefügt werden. Logische Operatoren zwischen den Positionen werden mit Hilfe von Schaltflächen ausgewählt. Dadurch wird eine Regel für diese Signalkombination festgelegt. Wenn Sie zum Beispiel Simple 1 AND Simple 2 einstellen, wird eine Signalkombination nur dann angezeigt, wenn diese beiden einfachen Signale gleichzeitig auftreten.


Implementieren der Funktionsweise

Vor dem Einführen neuer Ergänzungen in die Anwendung ist es notwendig, die vorhandenen Elemente für die Verwendung mit neuen Funktionen vorzubereiten. Die Schnittstellen-Elemente der Liste der einfachen Signale müssen geändert werden. Abbildung 3 zeigt, dass eine einfache Schaltfläche mit dem Systemnamen eines einfachen Signals durch eine Textbeschriftung mit einem nutzerspezifizierten Signalnamen ersetzt wird, sowie durch eine Bearbeiten-Schaltfläche und eine Schaltfläche für das Flag der Signalverwendung.


Abb. 3. Aktualisierung der Signalbearbeitung

Öffnen wir nun das vom vorherigen Artikel heruntergeladene Projekt und beginnen mit den Ergänzungen. Wir entfernen zunächst die Methode CreateSignalEditor() und das Array m_signal_editor[], das für die Erstellung von Editierbuttons verwendet wird. Bevor wir mit der Erstellung neuer Elemente und Methoden fortfahren, wollen wir zwei Makro-Substitutionen einführen, die die maximal zulässige Anzahl von einfachen und kombinierten Signalen bestimmen.

#define SIMPLE_SIGNALS 10
#define COMPOSITE_SIGNALS 5

 Gehen Sie als Nächstes zum Ende des Hauptfensters CreateStepWindow() zum Erstellen des Methodenrumpfes und anstelle des folgenden Codes:

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

Implementieren wir die Anzeige und die Bearbeitung eines neuen Signals neu.

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

Die Implementierung hat zwei neue Arrays von CButton Klasseninstanzen: m_signal_ind[] und m_signal_type[], sowie das Array CTextLabel m_signal_label[]. Fügen wir sie zur Basisklasse des Projekts CProgramm hinzu.

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

Deklarieren und implementieren wir außerdem die neue Methode CreateSignalSet().

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

Daher haben wir eine Liste von einfachen Signalen mit der aktualisierten Interaktionsschnittstelle aus Abbildung 3 gebildet. Eine Liste der Signalkombinationen wird durch einfaches Hinzufügen zur Variablen m_c_signal_label[] und m_c_signal_ind[] zur Basisklasse und an die Methode CreateStepWindow() des untenstehenden Codes angefügt.

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

Die Werkzeuge zur Anzeige und Bearbeitung einfacher und zusammengesetzter Signale wurden neu geschaffen. Fügen wir nun in Schritt 3 der anfänglichen Anwendungseinrichtung eine Schaltfläche zum Erstellen von Signalkombination zusammen mit einem Textkopf hinzu. Dies geschieht, indem aus der Variablen m_add_signal ein statisches Array m_add_signal[2] erstellt wird, das eine Instanz der Klasse CButton ist und in der Schaltfläche AddSignal verwendet wird. Ersetzen wir den folgenden Code in der Methoden CreateStepWindow()

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

durch einen neuen, der eine Schaltfläche zur Erzeugung einer Signalkombination enthält:

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

Um die Kopfzeilen der Listen mit einfachen und zusammengesetzten Signalen anzuzeigen, verwandeln wir die Variablen m_signal_header in das statische Array m_signal_header[2]. Der vorherige Code:

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

sollte durch den neuen mit zwei Kopfzeilen ersetzt werden:

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

Nach all den oben genannten Änderungen sieht die aktualisierte Schnittstelle in Schritt 3 wie folgt aus:

Abb.4 Hinzufügen und Aktualisieren von Schaltflächen zum Erstellen von Handelssignalen 

Nun müssen wir die zuvor erstellten Objekte der Liste der einfachen Handelssignale mit dem Erstellungs- und Bearbeitungsereignis verknüpfen. Hier füge ich eine kurze Notiz hinzu. Da die Anzahl der Schnittstellen-Elemente wächst, wodurch auch die Anzahl der möglichen Nutzerinteraktionen zunimmt, wird der Körper von OnEvent() zu lang und es wird schwer verständlich, zu welchem Element ein Ereignis oder eine Gruppe von Ereignissen gehört. Deshalb beschloss ich, alle Schlüsselinteraktionen mit der Schnittstelle in geeignete Methoden zu packen. Dies bietet zwei Vorteile: OnEvent() enthält die Liste der Schlüsselereignisse, so dass auf die Logik und den Code jedes einzelnen Ereignisses leicht zugegriffen werden kann.

Wir suchen das Codefragment, das für das Hinzufügen eines neuen einfachen Handelssignals verantwortlich ist, und fügen den erforderlichen Code hinzu und verschieben ihn in die Methode AddSimpleSignal():

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

Wir rufen das in OnEvent() auf. Auf diese Weise reduzieren wir die Größe des Methodenrumpfes und strukturieren seinen Implementierungsteil. Abbildung 3 zeigt, dass die neue Implementierung die Möglichkeit unterstützen sollte, einen nutzerdefinierten Signalnamen zu setzen. Dies kann durch Hinzufügen des folgenden Feldes im einfachen Signalerstellungs- und Bearbeitungsfenster erfolgen. Wir erstellen eine neue Methode mit dem Namen CreateSignalName(), die ein Eingabefeld für den Signalnamen hinzufügt.

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

Vergessen wir auch nicht, das Eingabefeld für den einfachen Signalnamen zur Struktur SIGNAL hinzuzufügen.

uchar             signal_name[50];

Die Methoden SaveSignalSet() und LoadSignalSet() sollten ebenfalls aktualisiert werden, so dass sie zusätzlich zu den bestehenden Einstellungen den Signalnamen aufzeichnen. Bei der Erstellung eines neuen Signals sollte die Situation vermieden werden, dass ein neuer Signalname mit dem eines zuvor erstellten Signals übereinstimmt. Eine andere zu vermeidende Situation ist, wenn Parameter in der Datei eines früher erzeugten Signals gespeichert werden. Fügen wir also das zweite Argument first_save der Methode SaveSignalSet() hinzu. Es dient als Hinweis darauf, ob ein Signal zum ersten Mal gespeichert wird oder editierte Parameter eines früher erzeugten Signals gespeichert werden.

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

Die vollständige Implementierung der Methode sieht wie folgt aus:

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

Im obigen Code habe ich die Methode zur Prüfung auf das Vorzeichen eines neuen Signals und die Methode CheckSignalNames() hervorgehoben, die prüft, ob ein Signal mit dem angegebenen Namen bereits existiert. Der weitere Code prüft, ob ein Signalname vorhanden ist und ob seine Länge mindestens drei Zeichen beträgt.

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

Jetzt können wir einfache Handelssignale in dem neuen Format erstellen, wie es im untenstehenden Screenshot zu sehen ist. Aktivieren wir die Schaltflächen zur Bearbeitung der erstellten Signale und sehen wir uns an, wie ein Verwendungszeichen einem einfachen Signal zugeordnet wird.

Abb.5 Neues Format der erzeugten einfachen Handelssignale 

Um die Bearbeitung einfacher Signale zu ermöglichen, erstellen wir eine neue Methode EditSimpleSignal() und rufen sie in OnEvent() auf. 

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

Ähnlich verhält es sich mit dem Umschalten des Verwendungszeichens, indem Sie eine neue Methode SignalSwitch() erstellen und diese im Handler aufrufen.

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

Der Schalter ist in Abbildung 6 unten dargestellt. Er wird durch einen einfachen Knopfdruck ausgeführt.

Abb.6 Visuelle Umschaltung des Verwendungszeichens eines einfachen Signals

Handelssignale sind nun bereit, als logische Elemente zusammengesetzter Handelssignale verwendet zu werden.

Zuvor haben wir eine Schaltfläche zum Erstellen einer neuen Signalkombination hinzugefügt - 'Add Composite Signal'. Jetzt müssen wir ein Dialogfenster implementieren, das es uns erlaubt, Signalkombinationen zu erstellen, einzurichten und zu bearbeiten. Zuvor haben wir bei der Erstellung eines einfachen Signalbearbeitungsfensters die Include-Datei SetWindow.mqh erstellt. Jetzt erstellen wir also CSetWindow.mqh, in der die Nutzeroberfläche des zukünftigen Editors für Signalkombinationen erstellt wird. Öffnen wir also die neu erstellte Datei und binden Programm.mqh ein, um auf die Basisklasse CProgramm zuzugreifen.

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

In die Datei Program.mqh binden wir die existierende CSetWindow.mqh ein:

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

Um ein Dialogfenster zu erstellen, führen wir eine neue Methode in der CreateCompositeEdit() Basisklasse ein und implementieren sie in der neu erstellten Include-Datei.

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

Jetzt fügen wir den Methodenaufruf in der Hauptschnittstellenerstellungsmethode hinzu.

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

Das erstellte Fenster ist ein Dialog und sollte sich daher bei einem bestimmten Ereignis öffnen, nämlich bei einem Klick auf die Schaltfläche 'Add Composite Signal'. Wir erstellen die Methode OpenCompositeSignalEditor() und fügen zu Demonstrationszwecken einen Befehl zu seinem Körper hinzu. Die Methode muss in der Ereignisbehandlung aufgerufen werden.

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

Abbildung 7 zeigt das hinzugefügte Dialogfeld, das durch einen Klick auf die Schaltfläche geöffnet wird.

Abb.7 Hinzufügen eines Dialogfensters zur Erzeugung von Signalkombinationen

Nun müssen wir das Dialogfenster mit den in Abbildung 2 gezeigten Schnittstellen-Elemente füllen:

  • Feld zur Eingabe des Namens der Signalkombination.
  • Die Liste der verfügbaren einfachen Signale zur Erstellung eines zusammengesetzten Signals.
  • Schnittstelle für das Erstellen einer logischen Regel für eine Signalkombination.

Alle neuen Methoden werden zur Basisklasse CProgramm hinzugefügt und in der Datei CSetWindow.mqh implementiert. Die Methode wird in der gleichen Datei im Körper der Dialogerstellungsmethode angewendet. Zuerst erstellen wir die Methode CreateCSignalName() ein Eingabefeld, implementieren sie und fügen sie dem Dialogfenster hinzu.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
}
Mit der neuen Methode CreateSimpleSignal() erstellen wir einen Satz von Schaltflächen mit zwei Zuständen. Sie werden verwendet, um einfache Signale auszuwählen, die zu den Positionen (Abb.2) für eine Signalkombination hinzugefügt werden sollen.

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

Die Methode CreateCompositeEdit() schaut wie folgt aus:

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

Die Schaltflächen kennen zwei Zustände: ein verwendetes, einfaches Signal (rot) und ein nicht verwendetes Signal (blau). Dies ist aber immer noch eine Vorlage.

Abb. 8 Vorlage zur Auswahl einfacher Handelssignale

Der nächste Schritt besteht darin, die folgende Logik auf die Vorlage anzuwenden:

  • Wenn 'Add Composite Signal' gedrückt wird, muss geprüft werden, ob mindestens zwei erzeugte Signale verfügbar sind.
  • Außerdem muss die Liste der bereits erstellten einfachen Signale mindestens zwei Signale mit dem Attribut entweder C (Composite) oder B (Both) enthalten.
  • Wenn die obigen Bedingungen erfüllt sind, werden die verfügbaren einfachen Signale angezeigt, die zur Bildung eines zusammengesetzten Signals verwendet werden können.

Um alle diese Bedingungen zu erfüllen, erstellen Sie eine spezielle Verifikationsmethode LoadForCompositeSignal(), die nur korrekt ausgewählte Signale anzeigt.

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

Sie sollte in der zuvor erstellten Methode OpenCompositeSignalEditor() angewendet werden:

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

Als Ergebnis (Abb. 9) haben wir eine korrekte Auswahl von Signalen, die unsere Bedingungen erfüllen. Wie man sehen kann, wird das einfache Signal mit dem Namen Test 2 nicht für die Erstellung eines zusammengesetzten Signals ausgewählt, da es das Attribut S (Simple) hat.

Abb. 9 Auswahl einfacher Signale für die Verwendung von Signalkombinationen

Jetzt müssen wir das System hinzufügen, das aus ausgewählten einfachen Signalen ein zusammengesetztes Signal bildet. Beginnen wir mit den Elementen der Schnittstelle. Es wird zwei Arten von Elementen geben: 

  • Positionen. Drei Positionen zum Platzieren ausgewählter einfacher Signale.
  • Logische Operatoren. Zwei Umschalttasten mit drei Zuständen AND, OR und (OR).

Die beiden ähnlichen Operatoren, OR und (OR), sind eigentlich verschieden. Der logische Operator OR kombiniert mit AND kann unterschiedlich interpretiert werden, wenn sie drei Ausdrücke verbinden. Abbildung 10 zeigt ein Beispiel, bei dem das Ergebnis eines logischen Ausdrucks je nach den Klammern unterschiedlich sein kann.


Abb. 10 Der Unterschied zwischen OR und (OR)

Fügen wir die Positionen und logische Operatoren zum Fenster für die Erzeugung und Bearbeitung von Signalkombinationen hinzu. Implementation der Methode CreateRuleSlot() zur Erzeugung der Positionen:

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

Für logische Operatoren erstellen wir die Methode CreateRuleSelector():

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

Wir fügen diese beiden Methoden zur Implementierung des Dialogfensters der Methode CreateCompositeEdit() hinzu.

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

In diesem Schritt sieht der visuelle Teil des Blocks, der die Logik der Signalkombinationen erzeugt, wie folgt aus:

Abb. 11 Visueller Teil der Schnittstelle zur Erstellung der Logik der Signalkombinationen

Um die logischen Schalter zu aktivieren, fügen wir die Methode LogicSwitch() hinzu und implementieren sie in der Ereignisbehandlung. 

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

Kommen wir nun zu einem weiteren wichtigen Punkt im Zusammenhang mit dem Ort der oben genannten Positionen. Wie füllt man sie in der erforderlichen Reihenfolge aus? Wählen Sie ein einfaches Signal aus der Liste der verfügbaren Signale und klicken Sie es an. Die Farbe der ersten Position wechselt zu blau und zeigt den Namen des hinzugefügten Signals an. Ein Klick auf das zweite Signal füllt die zweite Position aus. Wenn die Positionen ausgefüllt sind und Sie auf das bereits hinzugefügte Signal in der Liste klicken, wird die von ihm belegte Position freigegeben. Um diesen Mechanismus zu implementieren, erstellen wir die Methode SignalSelection() und fügen sie zur Ereignisbehandlung hinzu:

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

Die obigen Aktionen sind in Abbildung 12 dargestellt. 

Abb. 12 Ein Beispiel für die Erstellung eines zusammengesetzten Handelssignals unter Verwendung einfacher Signale

Jetzt müssen wir Optionen für die Einstellung der Anzeige des zusammengesetzten Signals auf dem Monitor hinzufügen. Die Optionen werden dem Fenster zur Erzeugung der Signalkombination hinzugefügt. Wir werden einen Abschnitt mit Elementen erstellen, der dem im Fenster zur einfachen Signalerzeugung ähnlich ist. Die Prinzipien der Einstellungserstellung wurden im zweiten Artikel besprochen. Abb. 13 unten zeigt das endgültige Aussehen des Bearbeitungsfensters für zusammengesetzte Signale.

Abb. 13 Die endgültige Version des Fensters zur Erzeugung der Signalkombination

Wir haben den visuellen Teil vorbereitet. Jetzt müssen wir die Berechnungskomponente integrieren, wofür wir den Mechanismus zur Speicherung und Bearbeitung von Composite-Signalen verwenden werden. Sobald die erforderlichen Parameter eingestellt sind (wie in Abb.13 dargestellt), speichert ein Klick auf die Schaltfläche Add die angegebenen Parameter in einer Datei. Die Funktion, die die obige Funktionalität implementiert, wird unten hinzugefügt. Sie heißt 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);
}

Ein Versuch, das Projekt jetzt zu kompilieren, wird drei Hauptfehler produzieren: Fehlen der Variablen m_c_signal_set und von zwei Prüfmethoden — CheckCSignalNames() und CheckCorrectSlots(). Der Variablentyp ist die neue Struktur C_SIGNAL, die für die Speicherung eines Satzes von zusammengesetzten Signalparametern erstellt wurde:

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

Die Methode CheckCSignalNames() ist der Methode CheckSignalNames() sehr ähnlich: sie prüft, ob der angegebene Name der Signalkombination eindeutig ist. Die neue Methode CheckCorrectSlots() prüft die Integrität und Korrektheit der erzeugten logischen Konstruktion des zusammengesetzten Signals:

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

Die notwendige Funktionsweise zur Erzeugung einer neuen Signalkombination ist fertig, so dass wir nun die Methode AddCompositeSignal() entwickeln werden:

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

Bevor wir die Funktionen zur Bearbeitung des erstellten Handelssignals erstellen, wollen wir einen Mechanismus zum Hochladen von Einstellungen aus einer Datei in das Bearbeitungsfenster für Signalkombinationen hinzufügen. Dies geschieht durch die Methode LoadCompositeSignalSet():

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadCompositeSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_c_signal_set[index]);
   FileReadStruct(h,m_c_signal_set[index]);
//--- Loading the indicator name
   m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name));
   m_c_signal_name.GetTextBoxPointer().Update(true);
//--- Slot values
   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);
}

Jetzt können wir die Möglichkeit hinzufügen, das erzeugte Signal mit der neuen Methode EditCompositeSignal() zu bearbeiten:

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

Die Software-Implementierung der Funktionen zum Erstellen, Speichern, Bearbeiten und Laden von Composite-Signalen ist fertig. Der letzte Schritt der Anwendungsentwicklung bleibt noch übrig: das Hinzufügen der erstellten Signalkombination zur Schnittstelle. In früheren Versionen wurde dies mit der Methode SearchSignals() durchgeführt, also modifizieren wir sie. Der Methodencode wurde so gestaltet, dass ein Maximum an Verständlichkeit und Klarheit gewährleistet ist — dies wurde durch die Einführung zusätzlicher Hilfsmethoden umgesetzt. Dennoch wird er in zwei logische Blöcke aufgeteilt: Suche nach einfachen Signalen und Suche nach zusammengesetzten Signalen. Betrachten wir den ersten Teil, der der Suche nach einfachen Signalen gewidmet ist. Achten Sie auf Änderungen im 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);
      }
   }
.....

Wir haben bereits früher einfache Signalverwendungsattribute eingeführt, daher müssen wir das Filtern von einfachen Signalen hinzufügen, um diejenigen zu überspringen, die nur als Teil einer Signalkombination verwendet werden sollen. Das Signal GetSimpleSignal() wurde hinzugefügt, um Einstellungen für jedes einfache Signal aus der angegebenen Datei zu erhalten und diese in die Struktur zu schreiben. Zwei weitere neue Methoden, SetVisualSignal() und SetDefaultVisual() implementieren die Anzeige der spezifizierten visuellen Einstellung entsprechend dem aktuell gefundenen Signal. Wenn kein Signal vorhanden ist, wird der Standard-Signalblock angezeigt. Wenn wir die neue Implementierung der einfachen Signalsuche mit der alten vergleichen, ist die neue Version leichter verständlich und fast dreimal kürzer geworden.

Lassen Sie uns nun zum zweiten Teil der Signalsuchmethode übergehen, der nach zusammengesetzten Handelssignalen sucht.

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

Die Suche nach zusammengesetzten Signalen erforderte auch zusätzliche Methoden sowohl zur Überprüfung als auch zur Visualisierung auf dem Monitor. Die Prüfmethode lautet GetUsedSlots(). Sie bestimmt den Typ des im Suchsystem verwendeten zusammengesetzten Signals: ob es aus zwei oder drei einfachen Signalen besteht.

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

Die zweite Methode SetVisualCompositeSignal() zeigt die gefundene Signalkombination im Signalblock als einen Satz visueller Parameter der aktuellen Signalkombination an.

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

Der Signalmonitor ist jetzt vollständig fertig. Er kann in Zukunft weiter geändert oder verfeinert werden, um Vorschläge der Nutzer umzusetzen oder zusätzliche Funktionen hinzuzufügen.

Frühere Artikel in dieser Serie:

Schlussfolgerung

Das angehängte Archiv enthält alle besprochenen Dateien, die sich in den entsprechenden Ordnern befinden. Für ihren ordnungsgemäßen Betrieb brauchen Sie nur den MQL5-Ordner im Terminalordner zu speichern. Um das Stammverzeichnis des Terminals, in dem sich der MQL5-Ordner befindet, zu öffnen, drücken Sie die Tastenkombination Strg+Umschalt+D im Terminal MetaTrader 5 oder verwenden Sie das Kontextmenü, wie in Abb. 14 unten dargestellt.


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


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/7759

Beigefügte Dateien |
MQL5.zip (2009.3 KB)
Zeitreihen in der Bibliothek DoEasy (Teil 42): Abstrakte Objektklasse der Indikatorpuffer Zeitreihen in der Bibliothek DoEasy (Teil 42): Abstrakte Objektklasse der Indikatorpuffer

In diesem Artikel beginnen wir mit der Entwicklung der Indikatorpufferklassen für die DoEasy-Bibliothek. Wir werden die Basisklasse des abstrakten Puffers erstellen, die als Grundlage für die Entwicklung verschiedener Klassentypen von Indikatorpuffern verwendet werden soll.

Zeitreihen in der Bibliothek DoEasy (Teil 41): Beispiel eines Multisymbol- und Mehrperiodenindikators Zeitreihen in der Bibliothek DoEasy (Teil 41): Beispiel eines Multisymbol- und Mehrperiodenindikators

In diesem Artikel entwickeln wir das Beispiel eines Multisymbol- und Mehrperiodenindikators, der die Zeitreihenklassen der DoEasy-Bibliothek verwendet und das Chart eines ausgewählten Währungspaares in einem ausgewählten Zeitrahmen als Kerzen in einem Unterfenster anzeigt. Ich werde die Bibliotheksklassen ein wenig modifizieren und eine separate Datei erstellen, zum Speichern von Enumerationen für die Programmeingaben und für die Auswahl einer Kompilierungssprache.

Zeitreihen in der Bibliothek DoEasy (Teil 43): Klassen der Objekte von Indikatorpuffern Zeitreihen in der Bibliothek DoEasy (Teil 43): Klassen der Objekte von Indikatorpuffern

Der Artikel beschäftigt sich mit der Entwicklung von Indikatorpuffer-Objektklassen, abgeleitet vom abstrakten Pufferobjekt, um die Deklaration zu vereinfachen und mit Indikatorpuffern zu arbeiten, während gleichzeitig nutzerdefinierte Indikatorprogramme auf der Grundlage der Bibliothek DoEasy erstellt werden.

MQL als Darstellungsmittel für graphische Schnittstellen von MQL-Programmen (Teil 3). Formular-Designer MQL als Darstellungsmittel für graphische Schnittstellen von MQL-Programmen (Teil 3). Formular-Designer

In diesem Artikel schließen wir die Beschreibung unseres Konzepts zum Aufbau der Fensterschnittstelle von MQL-Programmen unter Verwendung der Strukturen von MQL ab. Ein spezialisierter grafischer Editor erlaubt es, das Layout, das aus den Basisklassen der GUI-Elemente besteht, interaktiv zu erstellen und es dann in die MQL-Beschreibung zu exportieren, um es in Ihrem MQL-Projekt zu verwenden. Hier stellen wir das interne Design des Editors und ein Benutzerhandbuch vor. Die Quellcodes sind beigefügt.