English Русский 中文 Español 日本語 Português
Strategieentwickler auf Basis der Merill-Muster

Strategieentwickler auf Basis der Merill-Muster

MetaTrader 5Handel | 14 November 2019, 07:25
787 4
Alexander Fedosov
Alexander Fedosov

Inhaltsverzeichnis

Einführung

Im vorherigen Artikel haben wir die Anwendung der Merill-Muster auf verschiedene Daten erwogen, wie z.B. auf einen Preiswert auf dem Chart eines Währungssymbols und auf Werte von Standard-MetaTrader-5-Indikatoren: ATR, WPR, CCI, RSI, unter anderem. Um diese Idee zu erforschen, wurde eine grafische Oberfläche entwickelt. Der Hauptzweck der Schnittstelle ist es, effiziente Methoden zur Musterverwendung zu testen und zu finden. Weiterhin sollten die gefundenen erfolgreichen Konfigurationen in Handelsstrategien integriert und getestet werden. In diesem Artikel werden wir ein grundlegendes Toolkit entwickeln, um Handelsstrategien aufzubauen und zu testen.


Aufgabenbeschreibung und Anwendungsprototyp

Bevor Sie mit der Entwicklung der Anwendung beginnen, lassen Sie uns eine Liste der erforderlichen Funktionen und Oberflächenelemente erstellen. Zuerst definieren wir zwei Abschnitte:

  • Registerkarte Konstruktor
  • Registerkarte Einstellungen

Die Registerkarte Konstrukteur enthält die wichtigsten Oberflächenelemente, die zur Bildung einer Handelsstrategie erforderlich sind. Berücksichtige alle Grundelemente:

  • Symboltabelle, die aus einer vollständigen Liste der im Terminal verfügbaren Symbole besteht, unter der Registerkarte Marktübersicht.
  • Filteroption für die Symboltabelle, die eine komfortable Suche nach gewünschten Symbolgruppen ermöglicht.
  • Zwei identische Sektionen zum Erzeugen von Kauf- und Verkaufssignalen. Die Abschnitte enthalten identische Elemente, so dass im Folgenden nur eine Abschnittsbeschreibung angegeben wird.
  • Datumsbereich für die Prüfung.
  • Auswahl des aktuellen Zeitrahmens.
  • Der letzte Abschnitt unter der Registerkarte Konstruktor ist der Block mit Testergebnissen der festgelegten Handelsstrategie.

Betrachten wir den Abschnitt Signalerzeugung:

  1. Ein Muster, das aus dem Satz von Merill-Mustern ausgewählt wurde.
  2. Ein Satz von drei Signalen, auf die die Muster angewendet werden können. Jedes der Signale kann deaktiviert werden. D.h. wenn nur ein Signal ausgewählt wird, erfolgt der Markteintritt auf Basis dieses Signals. Wenn mehr Signale ausgewählt sind, wird die Eingabe von einem dieser Signale durchgeführt. Merill-Muster können sowohl auf Indikatoren als auch auf den Preis angewendet werden. 
  3. Einstellung von Take-Profit und Stop-Loss.

Die Registerkarte Einstellungen wird für standardmäßigen Indikatorparameter verwendet und ermöglicht das Hochladen von nutzerdefinierten Indikatoren.

Basierend auf den oben genannten gewünschten Eigenschaften erstellen wir einen Prototyp unserer Anwendung. Abbildung 1 unten zeigt das Schema und einen Satz von Oberflächenelementen für die Registerkarte Konstruktor:


Abb.1 Prototyp der Registerkarte Konstruktor und der Oberflächenelemente.

Lassen Sie uns auch einen Prototyp für die Registerkarte Einstellungen erstellen und Elemente in dieser Registerkarte anordnen. 


Abb.2 Prototyp der Registerkarte Einstellungen und Oberflächenelemente.

Achten Sie auf den zweiten Unterabschnitt, die Einstellungen für benutzerdefinierte Indikatoren und das Eingabefeld. Die Eingabe der korrekten Werte und die Bedienung einer Anzeige eines Drittanbieters entsprechen der Funktionssyntax von iCustom. Zunächst einmal geht es um die korrekte Eingabe des Indikatorpfades:

name

[in] Name des Benutzerindikators mit dem Pfad in Bezug auf Ordnerverzeichnis der Indikators (MQL5/Indicators/). Wenn ein Indikator in einem Untervezeichnis liegt, zB. in MQL5/Indicators/Examples, muss der Name dementsprechend aussehen: "Examples\\Name_des Indikators" (es ist notwendig, doppelte statt nur einfache slash als Begrenzer zu verwenden).

Um einen Indikator eines Drittanbieters auf eines der Signale anzuwenden, wählen Sie in der Dropdown-Liste für eines der Signale die Option Benutzerdefiniert, wie in Abb. 3 unten gezeigt:

Abb.3 Auswahl der Verwendung eines benutzerdefinierten Indikators.

Die vollständige Abfolge der Aktionen, die den Aufbau und die Verwendung des Strategieentwicklers betreffen, wird im Artikel nach der Softwareimplementierung der Anwendung näher beschrieben.

Implementierung eines Strategieentwicklers zum Testen

Bevor wir mit der Erstellung der grafischen Benutzeroberfläche der Anwendung fortfahren, lassen Sie uns ihre Grundelemente bestimmen, auf deren Grundlage die restlichen Elemente erstellt und entwickelt werden.

Die Anwendung hat zwei Fenster: das Hauptanwendungsfenster und ein Dialogfenster für die Einrichtung des Datumsbereichs. Das Hauptfenster hat zwei Registerkarten: Konstruktor und Einstellungen. Um dies zu implementieren, kombiniert die Hauptinterface-Erstellungsmethode CreateGUI() zwei Fenstererstellungsmethoden: 

  • CreateWindow() erstellt das Hauptfenster.
  • CreateDateSetting() erstellt das Fenster für die Einstellungen des Datumsbereichs.
//+------------------------------------------------------------------+
//| Erstellen der grafischen Schnittstelle des Programms             |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Panel erstelle
   if(!CreateWindow("Merrill Constructor"))
      return(false);
//--- Erstellen des Dialogfensters
   if(!CreateDateSetting())
      return(false);
//--- Beenden der Erstellung des GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Lassen Sie uns den Inhalt von jeder Methode betrachten. Die Methode CreateDateSetting() ist einfacher zu implementieren und enthält einfache Elemente. Das durch dieses Verfahren implementierte Interface-Element ist in Abb.4 separat dargestellt:

Abb.4 Dialogfeld Einstellungen für den Datumsbereich.

Das Fenster besteht aus dem Dialogfenster sowie zwei Kalenderelementen und zwei Elementen zur Einstellung der Start- und Endzeit. Wir haben die Elementinhalte definiert. Nun, lassen Sie es uns innerhalb der Methode CreateDate Setting() implementieren.

//+------------------------------------------------------------------+
//| Creates a date range selection dialog box                        |
//+------------------------------------------------------------------+
bool CProgram::CreateDateSetting(void)
{
//--- Hinzufügen eines Zeigers auf das Array des Fensters
   CWndContainer::AddWindow(m_window[1]);
//--- Koordinaten
   int x=m_date_range.X();
   int y=m_date_range.Y()+m_date_range.YSize();
//--- Eigenschaften
   m_window[1].XSize(372);
   m_window[1].YSize(230);
   m_window[1].WindowType(W_DIALOG);
   m_window[1].IsMovable(true);
//--- Erstellen des Formulars
   if(!m_window[1].CreateWindow(m_chart_id,m_subwin,"",x,y))
      return(false);
//---
   if(!CreateCalendar(m_calendar1,m_window[1],10,25,D'01.01.2019',1))
      return(false);
   if(!CreateCalendar(m_calendar2,m_window[1],201,25,m_calendar2.Today(),1))
      return(false);
//---
   if(!CreateTimeEdit(m_time_edit1,m_window[1],10,200,"Time",1))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[1],200,200,"Time",1))
      return(false);
//---
   return(true);
}

Kommen wir nun zur Methode CreateWindow(), die das Hauptanwendungsfenster implementiert. Die Methodenstruktur ist umfangreich, deshalb lassen Sie uns sie in einzelne Schlüsselkomponenten unterteilen. Die erste ist die Erstellung des Fensters selbst und seiner Grundstruktur, d.h. der beiden Registerkarten Konstruktor und Einstellungen. 

//+------------------------------------------------------------------+
//| Erstellen des Formulars eines Steuerelements                     |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
{
#define VERSION " 1.0"
   color caption=C'0,130,255';
   int ygap=30;
//--- Hinzufügen eines Zeigers auf das Array des Fensters
   CWndContainer::AddWindow(m_window[0]);
//--- Eigenschaften
   m_window[0].XSize(900);
   m_window[0].YSize(600);
   m_window[0].FontSize(9);
   m_window[0].CloseButtonIsUsed(true);
   m_window[0].CollapseButtonIsUsed(true);
   m_window[0].CaptionColor(caption);
   m_window[0].CaptionColorHover(caption);
   m_window[0].CaptionColorLocked(caption);
//--- Erstellen des Formulars
   if(!m_window[0].CreateWindow(m_chart_id,m_subwin,caption_text+VERSION,10,20))
      return(false);
//--- Registerkarten
   if(!CreateTabs(150,20))
      return(false);

Dieser Teil des Codes erzeugt das Hauptfenster, während die Methode CreateTabs() für das Hinzufügen der beiden oben beschriebenen Registerkarten verantwortlich ist:

//+------------------------------------------------------------------+
//| Erstellen einer Gruppe mit Registerkarten                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(const int x_gap,const int y_gap)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   m_tabs1.MainPointer(m_window[0]);
//--- Eigenschaften
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_LEFT);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoYResizeBottomOffset(25);
   m_tabs1.TabsYSize(40);   
//--- Hinzufügen von Registerkarten mit den angegebenen Eigenschaften
   for(int i=0; i<ArraySize(m_tabs_names); i++)
      m_tabs1.AddTab(m_tabs_names[i],150);
//--- Erstellen eines Steuerelements
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
}

Im obigen Prototyp haben wir den Inhalt der Elemente für jede der Registerkarten definiert: Konstruktor (Abb.1) und Einstellungen (Abb.2). Betrachten wir nun die Implementierung der in den Registerkarten enthaltenen Elemente. Die Registerkarte Konstruktor enthält viele sich wiederholende Elementtypen und so werden wir nur die wichtigsten Unterabschnitte und die Liste der Methoden betrachten, die für die Implementierung dieser Elemente verwendet werden.

//---- Constructor tab
//--- Symbols filter
   if(!CreateSymbolsFilter(10,10))
      return(false);
   if(!CreateSymbolsTable(10,45))
      return(false);
//--- Working timeframe
   if(!CreateTextLabel(m_text_labels1[2],290,10,"Timeframe",0))
      return(false);
   if(!CreateTimeframe1(440,10))
      return(false);
//--- Date range
   if(!CreateButton(m_date_range,240,10))
      return(false);
//--- Textbezeichner
   if(!CreateTextLabel(m_text_labels1[0],int(0.35*(m_window[0].XSize()-150)-100),10+ygap,"BUY—Signal",0))
      return(false);
   if(!CreateTextLabel(m_text_labels1[1],int(0.75*(m_window[0].XSize()-150)-100),10+ygap,"SELL—Signal",0))
      return(false);
//--- Pattern selection
   if(!PatternType1(int(0.35*(m_window[0].XSize()-150)-100),40+ygap,0))
      return(false);
   if(!CreateCheckBox(m_checkbox[0],int(0.35*(m_window[0].XSize()-150)-120),45+ygap,"Pattern"))
      return(false);
   if(!PatternType2(int(0.75*(m_window[0].XSize()-150)-100),40+ygap,0))
      return(false);
   if(!CreateCheckBox(m_checkbox[1],int(0.75*(m_window[0].XSize()-150)-120),45+ygap,"Pattern"))
      return(false);
//--- Selecting the application of patterns
   if(!AppliedType1(int(0.35*(m_window[0].XSize()-150)-100),80+ygap))
      return(false);
   if(!AppliedType2(int(0.35*(m_window[0].XSize()-150)-100),50+33*2+ygap))
      return(false);
   if(!AppliedType3(int(0.35*(m_window[0].XSize()-150)-100),50+33*3+ygap))
      return(false);
   if(!AppliedType4(int(0.75*(m_window[0].XSize()-150)-100),80+ygap))
      return(false);
   if(!AppliedType5(int(0.75*(m_window[0].XSize()-150)-100),50+33*2+ygap))
      return(false);
   if(!AppliedType6(int(0.75*(m_window[0].XSize()-150)-100),50+33*3+ygap))
      return(false);
//--- Signal checkboxes
   for(int i=2; i<8; i++)
   {
      if(i<5)
         if(!CreateCheckBox(m_checkbox[i],int(0.35*(m_window[0].XSize()-150)-120),50+35*(i-1)+ygap,"Signal "+string(i-1)))
            return(false);
      if(i>=5)
         if(!CreateCheckBox(m_checkbox[i],int(0.75*(m_window[0].XSize()-150)-120),50+35*(i-4)+ygap,"Signal "+string(i-4)))
            return(false);
   }
//--- Take Profit and Stop Loss settings
   if(!CreateEditValue(m_takeprofit1,int(0.35*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Take Profit",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss1,int(0.35*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Stop Loss",500,0))
      return(false);
   if(!CreateEditValue(m_takeprofit2,int(0.75*(m_window[0].XSize()-150)-120),50+35*4+ygap,"Take Profit",500,0))
      return(false);
   if(!CreateEditValue(m_stoploss2,int(0.75*(m_window[0].XSize()-150)-120),50+35*5+ygap,"Stop Loss",500,0))
      return(false);
//--- Report
   if(!CreateReportFrame(m_frame[2],"",int(0.35*(m_window[0].XSize()-150)-120),60+35*6+ygap))
      return(false);
   for(int i=0; i<6; i++)
   {
      if(i<3)
         if(!CreateTextLabel(m_report_text[i],int(0.4*(m_window[0].XSize()-150)-120),60+35*(7+i)+ygap,"",0))
            return(false);
      if(i>=3)
         if(!CreateTextLabel(m_report_text[i],int(0.75*(m_window[0].XSize()-150)-120),60+35*(7+i-3)+ygap,"",0))
            return(false);
      m_report_text[i].IsCenterText(false);
   }

Lassen Sie uns sehen, was die wichtigsten Interface-Teile implementieren und aus welchen Methoden sie bestehen.

1. Symbolfilter.

Besteht aus den Methoden CreateSymbolsFilter() und CreateSymbolsTable(). Sie implementieren das folgende Element:

Abb.5 Symbolfilter.

CreateSymbolsFilter() implementiert ein Eingabefeld mit einem Kontrollkästchen und einer Schaltfläche für eine Suche.

//+------------------------------------------------------------------+
//| Creates a checkbox with the "Symbols filter" input field         |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   m_symb_filter.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- Eigenschaften
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.YSize(25);
   m_symb_filter.FontSize(11);
   m_symb_filter.XSize(200);
   m_symb_filter.GetTextBoxPointer().XGap(20);
   m_symb_filter.GetTextBoxPointer().XSize(100);
   m_symb_filter.GetTextBoxPointer().YSize(25);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
//--- Erstellen eines Steuerelements
   if(!m_symb_filter.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Aktivieren des Ankreuzfelds
   m_symb_filter.IsPressed(true);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_symb_filter);
//---
   if(!CreateRequest(x_gap+125,y_gap))
      return(false);
   return(true);
}

CreateSymbolsTable() implementiert eine Tabelle, die gefilterte Währungssymbole aus dem Fenster der Marktübersicht ausgibt.

//+------------------------------------------------------------------+
//| Creates a symbol table                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
{
#define ROWS1_TOTAL    1
//--- Sichern des Zeigers auf das Hauptsteuerelement
   m_table_symb.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- Array of column widths
   int width[1]= {119};
//--- Array der Textausrichtungen in Spalten
   ENUM_ALIGN_MODE align[1]= {ALIGN_CENTER};
//--- Array of text offset along the X axis in the columns
   int text_x_offset[1]= {5};
//--- Eigenschaften
   m_table_symb.XSize(120);
   m_table_symb.TableSize(1,ROWS1_TOTAL);
   m_table_symb.ColumnsWidth(width);
   m_table_symb.TextAlign(align);
   m_table_symb.FontSize(10);
   m_table_symb.TextXOffset(text_x_offset);
   m_table_symb.ShowHeaders(true);
   m_table_symb.SelectableRow(true);
   m_table_symb.IsWithoutDeselect(true);
   m_table_symb.IsZebraFormatRows(clrWhiteSmoke);
   m_table_symb.AutoYResizeMode(true);
   m_table_symb.AutoYResizeBottomOffset(3);
   m_table_symb.HeadersColor(C'0,130,255');
   m_table_symb.HeadersColorHover(clrCornflowerBlue);
   m_table_symb.HeadersTextColor(clrWhite);
   m_table_symb.BorderColor(C'0,100,255');
//--- Erstellen eines Steuerelements
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
}

2. Arbeitszeitrahmen und die Schaltfläche "Date range" (Datumsbereich).

Alle Elemente implementieren die Auswahl eines Arbeitszeitrahmens für den Test. Die Schaltfläche Datumsbereich öffnet das entsprechende Dialogfeld, wie oben beschrieben. Die Methode CreateButton() implementiert die Schaltfläche. CreateTextLabel() erzeugt ein entsprechendes Label, CreateTimeframe1() implementiert die Zeitrahmenauswahl. CreateButton() und CreateTextLabel() sind universelle Methoden, die weiter verwendet werden. Ihr Code wird hier nur einmal angegeben. Die Elemente sind in Abb.6 separat dargestellt:

Abb.6 Die Schaltfläche Datumsbereich und die manuelle Zeitrahmenauswahl.

//+------------------------------------------------------------------+
//| Creates a text label in the first tab                            |
//+------------------------------------------------------------------+
bool CProgram::CreateTextLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text,int tab)
{
//--- Sichern des Fensterpointers
   text_label.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(tab,text_label);
//---
   text_label.Font("Trebuchet");
   text_label.FontSize(11);
   text_label.XSize(200);
   text_label.LabelColor(C'0,100,255');
   text_label.IsCenterText(true);
//--- Erstellen der Taste
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Pointers auf das Element auf die Datenbasis
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Die Schaltfläche, das Fenster der Zeitspannenauswahl anzuzeigen  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton(CButton &button,const int x_gap,const int y_gap)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   button.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,button);
//--- Eigenschaften
   button.XSize(100);
   button.YSize(25);
   button.FontSize(11);
   button.IsHighlighted(false);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
//--- Erstellen eines Steuerelements
   if(!button.CreateButton("Date range",x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Pointers auf das Element auf die Datenbasis
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

Die Methode CreateTimeframe1() ist eine Dropdown-Liste mit allen verfügbaren Zeitrahmen.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframe1(const int x_gap,const int y_gap)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_timeframe1.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,m_timeframe1);
//--- Array of the item values in the list view
   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"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_timeframe1.XSize(50);
   m_timeframe1.YSize(25);
   m_timeframe1.ItemsTotal(21);
   m_timeframe1.FontSize(12);
   m_timeframe1.LabelColor(C'0,100,255');
   CButton *but=m_timeframe1.GetButtonPointer();
   but.FontSize(10);
   but.XSize(50);
   but.BackColor(clrAliceBlue);
   but.XGap(1);
   m_timeframe1.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<21; i++)
      m_timeframe1.SetValue(i,timeframe_names[i]);
//--- Get the list view pointer
   CListView *lv=m_timeframe1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_timeframe1.SelectItem(0);
//--- Erstellen eines Steuerelements
   if(!m_timeframe1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_timeframe1);
   return(true);
}

3.  Textbeschriftungen in Abschnitten und Musterauswahlelemente für Kauf- und Verkaufssignale.

Textbeschriftungen werden mit der Methode CreateTextLabel() erstellt, die wir bereits berücksichtigt haben. Die beiden anderen Methoden implementieren die Kontrollkästchen und die Dropdown-Menüs zur Auswahl eines Merill-Musters für den Test.

Abb.7 Textbeschriftungen in Abschnitten und Musterauswahl.

Die Methode CreateCheckBox() erzeugt Muster-Checkboxen.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   checkbox.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,checkbox);
//--- Eigenschaften
   checkbox.YSize(25);
   checkbox.GreenCheckBox(true);
   checkbox.IsPressed(true);
   checkbox.FontSize(12);
   checkbox.LabelColor(C'0,100,255');
   checkbox.LabelColorPressed(C'0,100,255');
//--- Erstellen eines Steuerelements
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Pointers auf das Element auf die Datenbasis
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

Die Methoden PatternType1() und PatternType2() sind identisch.

//+------------------------------------------------------------------+
//| Creates combobox 1                                               |
//+------------------------------------------------------------------+
bool CProgram::PatternType1(const int x_gap,const int y_gap,const int tab)
{
//--- Total number of the list items
#define ITEMS_TOTAL1 32
//--- Übergebe das Objekt dem Bedienungsfeld
   m_combobox1.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(tab,m_combobox1);
//--- Array of the item values in the list view
   string pattern_names[ITEMS_TOTAL1]=
   {
      "M1","M2","M3","M4","M5","M6","M7","M8",
      "M9","M10","M11","M12","M13","M14","M15","M16",
      "W1","W2","W3","W4","W5","W6","W7","W8",
      "W9","W10","W11","W12","W13","W14","W15","W16"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_combobox1.XSize(200);
   m_combobox1.YSize(25);
   m_combobox1.ItemsTotal(ITEMS_TOTAL1);
   m_combobox1.GetButtonPointer().FontSize(10);
   m_combobox1.GetButtonPointer().BackColor(clrAliceBlue);
   m_combobox1.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<ITEMS_TOTAL1; i++)
      m_combobox1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_combobox1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_combobox1.SelectItem(0);
//--- Erstellen eines Steuerelements
   if(!m_combobox1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_combobox1);
   return(true);
}

4. Auswahl der Verwendung von Mustern und Signal-Checkboxen.

Dieser Schnittstellenblock besteht aus einem Satz von Signalen zur Konfiguration von Kauf- und Verkaufssignalen. Jeder Block besteht aus einem optionalen Wahlschalter aus einem bis drei Signalen, die als Eingangsbedingungen verwendet werden können. Bestehend aus den Methoden CreateCheckBox() und AppliedTypeN().


Abb.8 Verwendung von Mustern und Signal-Checkboxen. 

Die Strukturen der Methoden AppliedType1()-AppliedType6() sind ähnlich: Sie stellen eine Dropdown-Liste mit einer Auswahl eines Datenarrays zur Suche nach musterbasierten Signalen dar.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::AppliedType1(const int x_gap,const int y_gap)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_applied1.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(0,m_applied1);
//--- Array of the item values in the list view
   string pattern_names[9]=
   {
      "Price","ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","Custom"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_applied1.XSize(200);
   m_applied1.YSize(25);
   m_applied1.ItemsTotal(9);
   m_applied1.GetButtonPointer().FontSize(10);
   m_applied1.GetButtonPointer().BackColor(clrAliceBlue);
   m_applied1.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<9; i++)
      m_applied1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_applied1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_applied1.SelectItem(0);
//--- Erstellen eines Steuerelements
   if(!m_applied1.CreateComboBox("",x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_applied1);
   return(true);
}

5. Einstellungen von Take-Profit und Stop-Loss.

Der Schnittstellenbereich, der es ermöglicht, Take-Profit und Stop-Loss getrennt für Kaufsignale und Verkaufssignale zu konfigurieren. Die Level werden in Punkten festgelegt. 

Abb.9 Die Eingabefelder von Take-Profit und Stop-Loss.

Für die Implementierung dieser Eingabefelder wird die universelle Methode CreateEditValue() verwendet.

//+------------------------------------------------------------------+
//| Creates an input field                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap,const string label_text,const int value,const int tab)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   text_edit.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(tab,text_edit);
//--- Eigenschaften
   text_edit.XSize(210);
   text_edit.YSize(24);
   text_edit.LabelColor(C'0,100,255');
   text_edit.FontSize(12);
   text_edit.MaxValue(1000);
   text_edit.MinValue(10);
   text_edit.SpinEditMode(true);
   text_edit.SetValue((string)value);
   text_edit.GetTextBoxPointer().AutoSelectionMode(true);
   text_edit.GetTextBoxPointer().XGap(100);
//--- Erstellen eines Steuerelements
   if(!text_edit.CreateTextEdit(label_text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

6. Testergebnisse und Bericht.

Dieser Block besteht aus Testergebnissen. Sie wird mit der oben genannten Methode CreateTextLabel() implementiert. 

Abb.10 Berichtsblock.

Wir haben die Implementierung der Registerkarte Konstruktor berücksichtigt. Lassen Sie uns nun mit Einstellungen fortfahren.

1. Standard-Kennzeichenparameter.

Dieser Abschnitt enthält alle Einstellungen der Indikatoren, die für Tests und Analysen angeboten werden.

Abb.11 Block mit Standardeinstellungen des Indikators.

Dieser Block wird mit drei Methoden CreateFrame() implementiert, die einen visuellen Schnitt mit einem Rahmen erzeugen. Wir verwenden hier auch eine universelle Eingabefeldmethode zur Erstellung von Indikatorparametern CreateIndSetting() und einen Satz von IndicatorSetting1()-IndicatorSetting4() Methode für Dropdown-Listen für Ma-Methode, Volumen und Preisparameter.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateFrame(CFrame &frame,const string text,const int x_gap,const int y_gap)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   frame.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,frame);
//---
   frame.XSize(350);
   frame.YSize(500);
   frame.LabelColor(C'0,100,255');
   frame.BorderColor(C'0,100,255');
   frame.FontSize(11);
   frame.AutoYResizeMode(true);
   frame.AutoYResizeBottomOffset(100);
   frame.GetTextLabelPointer().XSize(250);
//--- Erstellen eines Steuerelements
   if(!frame.CreateFrame(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,frame);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting1(const int x_gap,const int y_gap,const string text)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_ind_set1.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,m_ind_set1);
//--- Array of the item values in the list view
   string pattern_names[4]=
   {
      "Simple","Exponential","Smoothed","Linear weighted"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_ind_set1.XSize(200);
   m_ind_set1.YSize(25);
   m_ind_set1.ItemsTotal(4);
   m_ind_set1.FontSize(12);
   m_ind_set1.LabelColor(C'0,100,255');
   CButton *but=m_ind_set1.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set1.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<4; i++)
      m_ind_set1.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set1.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   m_ind_set1.SelectItem(0);
//--- Erstellen eines Steuerelements
   if(!m_ind_set1.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_ind_set1);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting3(const int x_gap,const int y_gap,const string text)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_ind_set3.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,m_ind_set3);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Tick volume","Real Volume"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_ind_set3.XSize(200);
   m_ind_set3.YSize(25);
   m_ind_set3.ItemsTotal(2);
   m_ind_set3.FontSize(12);
   m_ind_set3.LabelColor(C'0,100,255');
   CButton *but=m_ind_set3.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set3.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_ind_set3.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set3.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_ind_set3.SelectItem(0);
//--- Erstellen eines Steuerelements
   if(!m_ind_set3.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_ind_set3);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::IndicatorSetting4(const int x_gap,const int y_gap,const string text)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_ind_set4.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,m_ind_set4);
//--- Array of the item values in the list view
   string pattern_names[4]=
   {
      "Open","Close","High","Low"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_ind_set4.XSize(200);
   m_ind_set4.YSize(25);
   m_ind_set4.ItemsTotal(4);
   m_ind_set4.FontSize(12);
   m_ind_set4.LabelColor(C'0,100,255');
   CButton *but=m_ind_set4.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   m_ind_set4.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<4; i++)
      m_ind_set4.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_ind_set4.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(82);
   m_ind_set4.SelectItem(1);
//--- Erstellen eines Steuerelements
   if(!m_ind_set4.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_ind_set4);
   return(true);
}

 2. Sprache der Nutzeroberfläche.

Das Steuerelement "Interface Language" (Schnittstellensprache) ist als Dropdown-Liste mit zwei Optionen implementiert: Englisch und Russisch. Dieses Element wird mit der Methode LanguageSetting() implementiert:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LanguageSetting(const int x_gap,const int y_gap,const string text)
{
//--- Übergebe das Objekt dem Bedienungsfeld
   m_language_set.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,m_language_set);
//--- Array of the item values in the list view
   string pattern_names[2]=
   {
      "Русский","English"
   };
//--- Setze Eigenschaften vor der Erstellung
   m_language_set.XSize(200);
   m_language_set.YSize(25);
   m_language_set.ItemsTotal(2);
   m_language_set.FontSize(12);
   m_language_set.LabelColor(C'0,100,255');
   CButton *but=m_language_set.GetButtonPointer();
   but.FontSize(10);
   but.XSize(100);
   but.BackColor(clrAliceBlue);
   but.XGap(140);
   m_language_set.GetListViewPointer().FontSize(10);
//--- Save the item values in the combobox list view
   for(int i=0; i<2; i++)
      m_language_set.SetValue(i,pattern_names[i]);
//--- Get the list view pointer
   CListView *lv=m_language_set.GetListViewPointer();
//--- Set the list view properties
   lv.LightsHover(true);
   lv.ItemYSize(20);
   lv.YSize(42);
   m_language_set.SelectItem(1);
//--- Erstellen eines Steuerelements
   if(!m_language_set.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,m_language_set);
   return(true);
}

3. Benutzerdefinierte Indikatorparameter.

Besteht aus dem visuellen Abschnitt mit Kopf und Rahmen, der mit der oben genannten Methode CreateFrame() erstellt wird, und einem Eingabefeld für den mit CreateIndSetting() erzeugten Indikatorwert und einer neuen Methode CreateCustomEdit() zur Eingabe des Indikatornamens und einer kommagetrennten Liste seines Parameters.

Abb.12 Benutzerdefinierte Parameter des Indikators.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text)
{
//--- Sichern des Zeigers auf das Hauptsteuerelement
   text_edit.MainPointer(m_tabs1);
//--- Hinzufügen zur Registerkarte
   m_tabs1.AddToElementsArray(1,text_edit);
//--- Eigenschaften
   text_edit.XSize(100);
   text_edit.YSize(24);
   text_edit.LabelColor(C'0,100,255');
   CTextBox *box=text_edit.GetTextBoxPointer();
   box.AutoSelectionMode(true);
   box.XSize(325);
   box.XGap(1);
   box.DefaultTextColor(clrSilver);
   box.DefaultText(default_text);
//--- Erstellen eines Steuerelements
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Hinzufügen eines Objekts zum gemeinsamen Array der Objektgruppen
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}

Wir haben den visuellen Teil besprochen. Kommen wir nun zum Algorithmus für das Testen der konfigurierten Handelsstrategien.

Um den Algorithmus des Testens mit dieser Anwendung zu erklären, müssen wir die Reihenfolge der Aktionen festlegen, die es uns ermöglichen, einen Test korrekt durchzuführen und ein Ergebnis zu erhalten. Eine gut durchdachte Abfolge von Aktionen kann das Prinzip jeder Interaktion mit der Anwendungsoberfläche hervorheben.

Schritt 1. Auswahl der Sprache der Benutzeroberfläche

Gemäß unserer Implementierung ist diese Option unter der Registerkarte Einstellungen in einer Dropdown-Liste verfügbar. Lassen Sie mich beschreiben, wie die Oberflächensprache umgestellt wird. Dies geschieht durch ein benutzerdefiniertes Ereignis der Auswahl von Elementen der Combobox, das die Methode ChangeLanguage() aufruft.

//--- Selection of a combo box item
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      //--- Ändern der Schnittstellensprache
      if(ChangeLanguage(lparam))
         Update(true);
   }

Lassen Sie uns nun die Methode zur Änderung der Oberflächensprache betrachten. Obwohl die Methode etwas länger ist, ist ihre Idee einfach.

//+------------------------------------------------------------------+
//| Ändern der Schnittstellensprache                                 |
//+------------------------------------------------------------------+
bool CProgram::ChangeLanguage(const long id)
{
//--- Prüfen der Elemente-ID
   if(id!=m_language_set.Id())
      return(false);
   m_lang_index=m_language_set.GetListViewPointer().SelectedItemIndex();
//---
   if(m_lang_index==0)
   {
      //--- Constructor tab
      m_tabs1.Text(0,"Конструктор");
      m_tabs1.Text(1,"Настройки");
      m_table_symb.SetHeaderText(0,"Символ");
      m_request.LabelText("Поиск");
      m_date_range.LabelText("Диапазон дат");
      m_timeframe1.LabelText("Таймфрейм");
      for(int i=0; i<8; i++)
      {
         if(i<2)
            m_checkbox[i].LabelText("Паттерн");
         else if(i>=2 && i<5)
            m_checkbox[i].LabelText("Сигнал "+string(i-1));
         else if(i>=5)
            m_checkbox[i].LabelText("Сигнал "+string(i-4));
      }
      m_takeprofit1.LabelText("Тейк Профит");
      m_takeprofit2.LabelText("Тейк Профит");
      m_stoploss1.LabelText("Стоп Лосс");
      m_stoploss2.LabelText("Стоп Лосс");
      m_frame[2].GetTextLabelPointer().LabelText("Отчёт");
      string report_label[6]=
      {
         "Всего трейдов: ","Короткие трейды: ","Прибыльные трейды: ",
         "Прибыль в пунктах: ","Длинные трейды: ","Убыточные трейды: "
      };
      for(int i=0; i<6; i++)
         m_report_text[i].LabelText(report_label[i]+"-");
      //--- Settings tab
      m_frame[0].GetTextLabelPointer().LabelText("Настройки стандартных индикаторов");
      m_frame[1].GetTextLabelPointer().LabelText("Настройки кастомных индикаторов");
      m_custom_buffer.LabelText("Номер буфера");
      m_custom_path.GetTextBoxPointer().DefaultText("Введите адрес индикатора");
      m_custom_param.GetTextBoxPointer().DefaultText("Введите параметры индикатора через запятую");
      m_language_set.LabelText("Язык интерфейса");
      //--- Date Range window
      m_window[1].LabelText("Настройки диапазона дат");
      m_time_edit1.LabelText("Время");
      m_time_edit2.LabelText("Время");
      m_time_edit3.LabelText("Время");
      m_time_edit4.LabelText("Время");
      m_status_bar.SetValue(0,"Не выбран символ для анализа");
   }
   else
   {
      //--- Constructor tab
      m_tabs1.Text(0,"Constructor");
      m_tabs1.Text(1,"Settings");
      m_table_symb.SetHeaderText(0,"Symbol");
      m_request.LabelText("Search");
      m_date_range.LabelText("Date range");
      m_timeframe1.LabelText("Timeframe");
      for(int i=0; i<8; i++)
      {
         if(i<2)
            m_checkbox[i].LabelText("Pattern");
         else if(i>=2 && i<5)
            m_checkbox[i].LabelText("Signal "+string(i-1));
         else if(i>=5)
            m_checkbox[i].LabelText("Signal "+string(i-4));
      }
      m_takeprofit1.LabelText("Take Profit");
      m_takeprofit2.LabelText("Take Profit");
      m_stoploss1.LabelText("Stop Loss");
      m_stoploss2.LabelText("Stop Loss");
      m_frame[2].GetTextLabelPointer().LabelText("Report");
      string report_label[6]=
      {
         "Total trades: ","Short Trades: ","Profit Trades: ",
         "Profit in points: ","Long Trades: ","Loss Trades: "
      };
      for(int i=0; i<6; i++)
         m_report_text[i].LabelText(report_label[i]+"-");
      //--- Settings tab
      m_frame[0].GetTextLabelPointer().LabelText("Standard Indicator Settings");
      m_frame[1].GetTextLabelPointer().LabelText("Custom Indicator Settings");
      m_custom_buffer.LabelText("Buffer number");
      m_custom_path.GetTextBoxPointer().DefaultText("Enter the indicator path");
      m_custom_param.GetTextBoxPointer().DefaultText("Enter indicator parameters separated by commas");
      m_language_set.LabelText("Interface language");
      //--- Date Range window
      m_window[1].LabelText("Date Range Settings");
      m_time_edit1.LabelText("Time");
      m_time_edit2.LabelText("Time");
      m_time_edit3.LabelText("Time");
      m_time_edit4.LabelText("Time");
      m_status_bar.SetValue(0,"No symbol selected for analysis");
   }
   return(true);
}

Schritt 2. Einstellungen der Parameter des Indikators

Unter der gleichen Registerkarte werden die Werte der Indikatorparameter eingestellt, falls bestimmte Indikatoren getestet werden sollen. Optional werden benutzerdefinierte Indikatorparameter konfiguriert: Puffernummer, Name oder Parameter, die durch Kommas getrennt sind. Bitte beachten Sie, dass für benutzerdefinierte Indikatoren nur numerische Werte unterstützt werden.

Schritt 3. Einstellungen der Symboltabelle.

Konfigurieren Sie im oberen Teil der Registerkarte Konstruktor die erforderlichen Symbole, die im Fenster der Marktübersicht verfügbar sind. Dies geschieht mit der Methode RequestData(). Die Methode wird über den Button "Search" aufgerufen.

   //--- Button click event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
      //--- Requesting data
      RequestData(lparam);
....
//+------------------------------------------------------------------+
//| Output of symbols to the symbols table                           |
//+------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
{
//--- Prüfen der Elemente-ID
//---
   if(id==m_request.Id())
   {
      //--- Tabelle ausblenden
      m_table_symb.Hide();
      //--- Initialisierung der Tabelle
      GetSymbols(m_symb_filter);
      RebuildingTables(m_table_symb);
      //--- Einblenden der Tabelle
      m_table_symb.Show();
   }
   return(true);
}

Schritt 4. Auswahl des Testzeitraums

Dieses Ereignis tritt bei einem Klick auf die Schaltfläche "Date range" ein. Die Logik ist einfach: Es öffnet sich eine Dialogbox zur Einstellung des Datumsbereichs.

//--- Button click event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
...
      //---
      if(lparam==m_date_range.Id())
      {
         int x=m_date_range.X();
         int y=m_date_range.Y()+m_date_range.YSize();
         m_window[1].X(x);
         m_window[1].Y(y);
         m_window[1].OpenWindow();
         string val=(m_lang_index==0)?"Настройки диапазона дат":"Date Range Settings";
         m_window[1].LabelText(val);
      }
...

Seien Sie vorsichtig bei der Auswahl der Daten. Wenn die Daten falsch eingestellt sind, gibt die App Fehlermeldungen zurück. Zu den häufigsten Fehlern gehören folgende: Das Enddatum liegt nach dem aktuellen Datum im Terminal oder das Enddatum liegt vor dem Anfangsdatum.

Schritt 5. Einstellung des Arbeitszeitrahmens.

Der Arbeitszeitrahmen gilt für alle sechs Signale, die im Konstruktor konfiguriert werden können.

Schritt 6. Aktivieren von Verkaufs-/Kaufsignalen und Auswählen eines Musters zum Testen.

Die Tests werden standardmäßig in zwei Richtungen durchgeführt: Kauf und Verkauf. Einer der Modi kann jedoch deaktiviert werden, wie in Abbildung 13 unten gezeigt.

Abb.13 Kauf- oder Verkaufssignale deaktivieren.

Das Merill-Muster für weitere Tests kann links neben dem Muster-Label ausgewählt werden. Die Details der Merill-Muster wurden im vorherigen Artikel beschrieben.

Schritt 7. Auswahl der Signale für den Test und Einstellung Take-Profit und Stop-Loss

Abbildung 13 zeigt, dass für jeden Eröffnungstyp einer Position bis zu drei Signale gleichzeitig eingestellt werden können. Die Signale arbeiten nach dem logischen ODER-Prinzip. Wenn also alle drei Kaufsignale in einem Test gesetzt werden, wird ein Markteintritt registriert, wenn eines der drei Signale auftritt. Gleiches gilt für Verkaufssignale. In der Dropdown-Liste rechts neben den Signaltextbeschriftungen können Sie den Datentyp auswählen, auf den das ausgewählte Muster angewendet werden soll.

Schritt 8. Durchführung des Tests

Nach den Schritten 1-7 wählen Sie das Prüfgerät durch einen Linksklick in der Tabelle aus. Der Testalgorithmus wird durch ein benutzerdefiniertes Ereignis gestartet, bei dem Sie auf ein Listen- oder Tabellenelement klicken.

//--- Event of pressing on a list or table item
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
   {
      //--- Select a symbol for further work
      //--- Prüfen der Elemente-ID
      if(lparam==m_table_symb.Id())
      {
         //--- Verlassen, wenn die Zeile nicht ausgewählt wurde
         if(m_table_symb.SelectedItem()==WRONG_VALUE)
         {
            //--- Anzeige der gesamten Symbolbeschreibung in der Statusleiste
            m_status_bar.SetValue(0,"Не выбран символ для анализа");
            m_status_bar.GetItemPointer(0).Update(true);
         }
         //--- Get a selected symbol
         string symbol=m_table_symb.GetValue(0,m_table_symb.SelectedItem());
         //--- Anzeige der gesamten Symbolbeschreibung in der Statusleiste
         m_status_bar.SetValue(0,"Selected symbol: "+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
         m_status_bar.GetItemPointer(0).Update(true);
         GetResult(symbol);
      }
   }

Der Test wird mit der Methode GetResult() durchgeführt. Überlegen Sie es sich genauer.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::GetResult(const string symbol)
{
//--- Abrufen der Zeitspanne
   m_start_date=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   m_end_date=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00");
//--- Prüfen des angegebenen Zeitpunkts
   if(m_start_date>m_end_date || m_end_date>TimeCurrent())
   {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return;
   }
//--- Проверка выбора паттернов
   int buy_pat=m_combobox1.GetListViewPointer().SelectedItemIndex();
   int sell_pat=m_combobox2.GetListViewPointer().SelectedItemIndex();
   if(buy_pat==sell_pat)
   {
      if(m_lang_index==0)
         MessageBox("Паттерн на покупку и продажу не может быть одинаков!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("The pattern for buying and selling cannot be the same!","Error",MB_OK);
      return;
   }
//---
   ZeroMemory(m_report);
   datetime cur_date=m_start_date;
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int applied1=m_applied1.GetListViewPointer().SelectedItemIndex();
   int applied2=m_applied2.GetListViewPointer().SelectedItemIndex();
   int applied3=m_applied3.GetListViewPointer().SelectedItemIndex();
   int applied4=m_applied4.GetListViewPointer().SelectedItemIndex();
   int applied5=m_applied5.GetListViewPointer().SelectedItemIndex();
   int applied6=m_applied6.GetListViewPointer().SelectedItemIndex();
//---
   while(cur_date<m_end_date)
   {
      if(
         BuySignal(symbol,m_start_date,applied1,1) ||
         BuySignal(symbol,m_start_date,applied2,2) ||
         BuySignal(symbol,m_start_date,applied3,3))
      {
         CalculateBuyDeals(symbol,m_start_date);
         cur_date=m_start_date;
         continue;
      }
      if(
         SellSignal(symbol,m_start_date,applied4,1) ||
         SellSignal(symbol,m_start_date,applied5,2) ||
         SellSignal(symbol,m_start_date,applied6,3))
      {

         CalculateSellDeals(symbol,m_start_date);
         cur_date=m_start_date;
         continue;
      }
      m_start_date+=PeriodSeconds(StringToTimeframe(tf));
      cur_date=m_start_date;
   }
//--- Output the report
   PrintReport();
}

Diese Methode beinhaltet die Prüfung, ob der Datumsbereich korrekt eingestellt ist. Eine weitere Überprüfung wird durchgeführt, um sicherzustellen, dass der Benutzer nicht die gleichen Muster für die Prüfung von Kauf- und Verkaufssignalen festgelegt hat. Die Methode GetResult() beinhaltet drei Methoden zum Arbeiten mit Daten, die in den Einstellungen angegeben sind.

1. Methoden zur Signalsuche: BuySignal() und SellSignal(). Sie sind ähnlich. Betrachten wir einen davon.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::BuySignal(const string symbol,datetime start,int applied,int signal)
{
//--- Exit if the buy signal is disabled
   if(!m_checkbox[0].IsPressed())
      return(false);
//---
   int Handle=INVALID_HANDLE;
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
//--- Preparing data
   if(m_checkbox[signal+1].IsPressed())
   {
      //--- Price
      if(applied==0)
      {
         MqlRates rt[];
         int sl=0,tp=0;
         POINTS pat;
         double arr[];
         int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,5,rt);
         int app_price=m_ind_set4.GetListViewPointer().SelectedItemIndex();
         ArrayResize(arr,copied);
         //Print(m_start_date+": "+copied);
         if(copied<5)
            return(false);
         //---
         for(int i=0; i<copied; i++)
         {
            if(app_price==0)
               arr[i]=rt[i].open;
            else if(app_price==1)
               arr[i]=rt[i].close;
            else if(app_price==2)
               arr[i]=rt[i].high;
            else if(app_price==3)
               arr[i]=rt[i].low;
         }
         //--- Pattern search
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- If the pattern is found, check the signal
         if(GetPatternType(pat)==m_combobox1.GetListViewPointer().SelectedItemIndex())
         {
            m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),5);
            return(true);
         }
         return(false);
      }
      //--- ATR
      if(applied==1)
         Handle=iATR(symbol,StringToTimeframe(tf),int(m_ind_setting[0].GetValue()));
      //--- CCI
      if(applied==2)
      {
         int app_price;
         switch(m_ind_set4.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            app_price=PRICE_OPEN;
            break;
         case  1:
            app_price=PRICE_CLOSE;
            break;
         case  2:
            app_price=PRICE_HIGH;
            break;
         case  3:
            app_price=PRICE_LOW;
            break;
         default:
            app_price=PRICE_CLOSE;
            break;
         }
         Handle=iCCI(symbol,StringToTimeframe(tf),int(m_ind_setting[1].GetValue()),app_price);
      }
      //--- DeMarker
      if(applied==3)
         Handle=iDeMarker(symbol,StringToTimeframe(tf),int(m_ind_setting[2].GetValue()));
      //--- Force Index
      if(applied==4)
      {
         int force_period=int(m_ind_setting[3].GetValue());
         ENUM_MA_METHOD force_ma_method;
         ENUM_APPLIED_VOLUME force_applied_volume;
         switch(m_ind_set1.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            force_ma_method=MODE_SMA;
            break;
         case  1:
            force_ma_method=MODE_EMA;
            break;
         case  2:
            force_ma_method=MODE_SMMA;
            break;
         case  3:
            force_ma_method=MODE_LWMA;
            break;
         default:
            force_ma_method=MODE_SMA;
            break;
         }
         switch(m_ind_set3.GetListViewPointer().SelectedItemIndex())
         {
         case  0:
            force_applied_volume=VOLUME_TICK;
            break;
         case  1:
            force_applied_volume=VOLUME_REAL;
            break;
         default:
            force_applied_volume=VOLUME_TICK;
            break;
         }
         Handle=iForce(symbol,StringToTimeframe(tf),force_period,force_ma_method,force_applied_volume);
      }
      //--- WPR
      if(applied==5)
         Handle=iWPR(symbol,StringToTimeframe(tf),int(m_ind_setting[5].GetValue()));
      //--- RSI
      if(applied==6)
         Handle=iRSI(symbol,StringToTimeframe(tf),int(m_ind_setting[4].GetValue()),PRICE_CLOSE);
      //--- Momentum
      if(applied==7)
         Handle=iMomentum(symbol,StringToTimeframe(tf),int(m_ind_setting[6].GetValue()),PRICE_CLOSE);
      //--- Custom
      if(applied==8)
      {
         string str[];
         double arr[];
         string parameters=m_custom_param.GetValue();
         StringSplit(parameters,',',str);
         if(ArraySize(str)>20)
         {
            if(m_lang_index==0)
               MessageBox("Количество параметров не должно быть больше 20!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("The number of parameters should not be more than 20!","Error",MB_OK);
         }
         ArrayResize(arr,ArraySize(str));
         for(int i=0; i<ArraySize(str); i++)
            arr[i]=StringToDouble(str[i]);
         string name=m_custom_path.GetValue();
         Handle=GetCustomValue(StringToTimeframe(tf),name,arr);
      }
      //---
      if(applied>0)
      {
         if(Handle==INVALID_HANDLE)
         {
            if(m_lang_index==0)
               MessageBox("Не удалось получить хендл индикатора!","Ошибка",MB_OK);
            else if(m_lang_index==1)
               MessageBox("Failed to get indicator handle!","Error",MB_OK);
         }
         double arr[];
         int buffer=(applied==8)?int(m_custom_buffer.GetValue()):0;
         int copied=CopyBuffer(Handle,buffer,m_start_date,5,arr);
         //---
         int sl=0,tp=0;
         POINTS pat;
         if(copied<5)
            return(false);
         //--- Pattern search condition
         pat.A=arr[0];
         pat.B=arr[1];
         pat.C=arr[2];
         pat.D=arr[3];
         pat.E=arr[4];
         //--- If the pattern is found, check the signal
         if(GetPatternType(pat)==m_combobox1.GetListViewPointer().SelectedItemIndex())
         {
            m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),5);
            return(true);
         }
         return(false);
      }
      return(false);
   }
   return(false);
}

Die Idee der Methode liegt in der vorgegebenen Reihenfolge der Aktionen:

  • Überprüfung, ob ein Kaufsignal erlaubt ist und Überprüfung des spezifischen Signals.
  • Überprüfung des Datenarrays, auf das die Muster angewendet werden sollen.
  • Vorbereitung der Daten für die Suche und nach dem angegebenen Muster mit der Methode GetPatternType() suchen.

2. Verfahren zur Verarbeitung des gefundenen Signals CalculateBuyDeals() und CalculateSellDeals().

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::CalculateBuyDeals(const string symbol,datetime start)
{
   MqlRates rt[];
   int TP=int(m_takeprofit1.GetValue());
   int SL=int(m_stoploss1.GetValue());
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,m_end_date,rt);
   double deal_price=iOpen(symbol,StringToTimeframe(tf),copied);
   for(int j=0; j<copied; j++)
   {
      if((iHigh(symbol,StringToTimeframe(tf),copied-j)-deal_price)/SymbolInfoDouble(symbol,SYMBOL_POINT)>=TP)
      {
         m_report.profit_trades++;
         m_report.profit+=TP;
         m_report.long_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
      else if((deal_price-iLow(symbol,StringToTimeframe(tf),copied-j))/SymbolInfoDouble(symbol,SYMBOL_POINT)>=SL)
      {
         m_report.loss_trades++;
         m_report.profit-=SL;
         m_report.long_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
   }
   m_start_date=m_end_date;
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::CalculateSellDeals(const string symbol,datetime start)
{
   MqlRates rt[];
   int TP=int(m_takeprofit2.GetValue());
   int SL=int(m_stoploss2.GetValue());
   string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
   int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,m_end_date,rt);
   double deal_price=iOpen(symbol,StringToTimeframe(tf),copied);
   for(int j=0; j<copied; j++)
   {
      if((deal_price-iLow(symbol,StringToTimeframe(tf),copied-j))/SymbolInfoDouble(symbol,SYMBOL_POINT)>=TP)
      {
         m_report.profit_trades++;
         m_report.profit+=TP;
         m_report.short_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
      else if((iHigh(symbol,StringToTimeframe(tf),copied-j)-deal_price)/SymbolInfoDouble(symbol,SYMBOL_POINT)>=SL)
      {
         m_report.loss_trades++;
         m_report.profit-=SL;
         m_report.short_trades++;
         m_report.total_trades++;
         m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
         return;
      }
   }
   m_start_date=m_end_date;
}

Ihre Aufgabe ist es, das gefundene Signal zu verarbeiten und Statistiken aufzuzeichnen, anhand derer der Bericht erstellt wird.

3. Die Methode PrintReport(), die Testergebnisse ausgibt.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::PrintReport(void)
{
   if(m_lang_index==0)
   {
      string report_label[6]=
      {
         "Всего трейдов: ","Короткие трейды: ","Прибыльные трейды: ",
         "Прибыль в пунктах: ","Длинные трейды: ","Убыточные трейды: "
      };
      //---
      m_report_text[0].LabelText(report_label[0]+string(m_report.total_trades));
      m_report_text[1].LabelText(report_label[1]+string(m_report.short_trades));
      m_report_text[2].LabelText(report_label[2]+string(m_report.profit_trades));
      m_report_text[3].LabelText(report_label[3]+string(m_report.profit));
      m_report_text[4].LabelText(report_label[4]+string(m_report.long_trades));
      m_report_text[5].LabelText(report_label[5]+string(m_report.loss_trades));
   }
   else
   {
      string report_label[6]=
      {
         "Total trades: ","Short Trades: ","Profit Trades: ",
         "Profit in points: ","Long Trades: ","Loss Trades: "
      };
      //---
      m_report_text[0].LabelText(report_label[0]+string(m_report.total_trades));
      m_report_text[1].LabelText(report_label[1]+string(m_report.short_trades));
      m_report_text[2].LabelText(report_label[2]+string(m_report.profit_trades));
      m_report_text[3].LabelText(report_label[3]+string(m_report.profit));
      m_report_text[4].LabelText(report_label[4]+string(m_report.long_trades));
      m_report_text[5].LabelText(report_label[5]+string(m_report.loss_trades));
   }
   Update(true);
}

Das zeigt die Testdaten in der Anwendung an. Damit ist der Algorithmus vervollständigt.

Demonstration und Beispiel für den Betrieb des Strategieentwicklers

Als Beispiel entschied ich mich, ein kurzes Video aufzunehmen, das die Funktionsweise des Strategieentwicklers zeigt.


Schlussfolgerung

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


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


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/7218

Beigefügte Dateien |
MQL5.zip (506.48 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
WOLFRAM STEFFEN SIEGERT
WOLFRAM STEFFEN SIEGERT | 28 Jan. 2020 in 22:49

Ich habe das Zip-Archiv entpackt und alle Dateien an ihren Bestimmungsort kopiert.

Nach Compilierung wird der EA nicht geladen und es erscheint im Reiter Experten des Terminals die Meldung:

"CElement::CreateCanvas > Failed to create a canvas for drawing the (CButton) control: 4016"

Wer hat eine Idee den EA zum Laufen zu bekommen?


traderdoc

Christian
Christian | 29 Jan. 2020 in 17:53
WOLFRAM STEFFEN SIEGERT:

Ich habe das Zip-Archiv entpackt und alle Dateien an ihren Bestimmungsort kopiert.

Nach Compilierung wird der EA nicht geladen und es erscheint im Reiter Experten des Terminals die Meldung:

"CElement::CreateCanvas > Failed to create a canvas for drawing the (CButton) control: 4016"

Wer hat eine Idee den EA zum Laufen zu bekommen?


traderdoc

Wahrscheinlich nutzt du Build 2280.

In der Canvas.mqh ist ein Bug drin.

Entferne folgenden String "(string)CharId + "  aus der Zeile 254.

Danach neu kompilieren und der Fehler sollte nicht mehr auftreten.


Zeile 254 in Canvas.mqh nach der Korrektur:

m_rcname="::"+name+(string)(GetTickCount()+MathRand());


Gruß

WOLFRAM STEFFEN SIEGERT
WOLFRAM STEFFEN SIEGERT | 29 Jan. 2020 in 19:09

Ja vielen Dank!

Bis zu der Stelle war im inzwischen auch gekommen und hatte die urspünglichen Zeile

m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());

dann auf

m_rcname="::"+name+(string)ChartID();

verkürzt.

Das funktioniert auch.


traderdoc

Christian
Christian | 29 Jan. 2020 in 19:33
WOLFRAM STEFFEN SIEGERT:

Ja vielen Dank!

Bis zu der Stelle war im inzwischen auch gekommen und hatte die urspünglichen Zeile

m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());

dann auf

m_rcname="::"+name+(string)ChartID();

verkürzt.

Das funktioniert auch.


traderdoc

Im Prinzip ist nur der generierte Name zu lang.

Ob die Zufallskomponente GetTickCount() wichtig ist weiß ich nicht. Probiere oder nutze den ganzen GFX Kram nicht.

Bist du der traderdoc aus bekannten Foren ?

Gruß

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVII): Interaktivität von Bibliotheksobjekten Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVII): Interaktivität von Bibliotheksobjekten
In diesem Artikel werden wir die Entwicklung des Basisobjekts aller Bibliotheksobjekte abschließen, so dass jedes darauf basierende Bibliotheksobjekt mit einem Nutzer interagieren kann. So kann der Nutzer beispielsweise die maximal akzeptable Größe eines Spreads zum Eröffnen einer Position und eines Preisniveaus einstellen, bei dessen Erreichen ein Ereignis aus einem Symbolobjekt mit dem spread- oder preisniveauabhängigen Signal an das Programm gesendet wird.
Entwicklung eines plattformübergreifenden Grid-EAs (Letzter Teil): Diversifikation als Mittel zur Steigerung der Profitabilität Entwicklung eines plattformübergreifenden Grid-EAs (Letzter Teil): Diversifikation als Mittel zur Steigerung der Profitabilität
In früheren Artikeln dieser Serie haben wir verschiedene Methoden ausprobiert, um einen mehr oder weniger profitablen Grid-Expertenberater zu erstellen. Jetzt werden wir versuchen, die EA-Profitabilität durch Diversifikation zu steigern. Unser oberstes Ziel ist es, einen Jahresgewinn von 100% zu erreichen, wobei der maximale Drawdown des Saldos nicht mehr als 20% beträgt.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVIII): Interaktivität des Kontos und aller anderen Bibliotheksobjekte Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVIII): Interaktivität des Kontos und aller anderen Bibliotheksobjekte
Der Artikel reiht die Arbeit eines Kontoobjekts in ein neues Basisobjekt aller Bibliotheksobjekte ein, verbessert das Basisobjekt CBaseObj und testet die Einstellung von verfolgten Parametern sowie das Empfangen von Ereignissen für alle Bibliotheksobjekte.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVI): Ereignisse der Kollektionssymbole Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XVI): Ereignisse der Kollektionssymbole
In diesem Artikel werden wir eine neue Basisklasse aller Bibliotheksobjekte erstellen, die die Ereignisfunktionen allen ihren Nachkommen hinzufügt, und die Klasse zur Verfolgung von Ereignissen der Kollektionssymbole auf der Grundlage der neuen Basisklasse entwickeln. Wir werden auch die Konto- und Ereignisklassen für die Entwicklung der neuen Basisobjektfunktionalität ändern.