Die Handelssignale mehrerer Währungen überwachen (Teil 4): Erweiterung der Funktionsweise und Verbesserung des Signalsuchsystems
Inhalt
- Einführung
- Nutzerdefinierter Indikator zur Erzeugung eines Handelssignals
- Erweiterung des Systems der Suchregeln für Handelssignale
- Umwandlung der Symbolliste in eine Tabelle
- Schnelle Bearbeitung der Suchregeln im Monitor
- Lokalisierung der Anwendung
- Zusätzliche Eigenschaften
- Schlussfolgerung
Einführung
Im dritten Teil haben wir ein Grundsystem für die Suche nach Signalen geschaffen, das jedoch auf einem kleinen Satz von Indikatoren und einem einfachen Satz von Suchregeln beruhte. Außerdem erhielt ich Vorschläge für Verbesserungen der Nutzerfreundlichkeit, die im visuellen Teil des Handelsmonitors vorgenommen werden könnten. Das ist es, was wir in diesem Teil umsetzen werden.
Nutzerdefinierter Indikator zur Erzeugung eines Handelssignals
Eine logische Ergänzung zur Erstellung und Bearbeitung von Handelssignalen ist die Erweiterung des Satzes der verfügbaren Indikatoren. Bisher konnten wir nur mit Indikatoren aus dem Standardset von MetaTrader 5 arbeiten. Jetzt können wir die Möglichkeit hinzufügen, den Berechnungsteil von nutzerdefinierten Indikatoren zu verwenden. Lassen Sie uns das Projekt aus dem vorherigen Teil als Grundlage verwenden. Es kann aus der Anlage des Artikels heruntergeladen werden. In diesem Teil werden wir die Betriebsalgorithmen der Methoden der Basisklassen ändern müssen, die wir in Teil 3 betrachtet haben. Alle Änderungen und Ergänzungen werden mit entsprechenden Erläuterungen versehen.
Beginnen wir mit der Möglichkeit, einen nutzerdefinierten Indikator im Fenster zum Hinzufügen und Bearbeiten von Signalen auszuwählen. Diese Fensterimplementierung ist in der Datei SetWindow.mqh unseres Projekts vorgesehen. Öffnen Sie diese Datei und suchen Sie die Methode CreateIndicatorType(). Änderungen sollten genau in dieser Datei implementiert werden.
//+------------------------------------------------------------------+ //| Creates a drop-down menu with indicator types | //+------------------------------------------------------------------+ bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap) { //--- Pass the object to the panel m_indicator_type.MainPointer(m_set_window); //--- #define SIZE 10 //--- Array of the item values in the list view string pattern_names[SIZE]= { "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum","ADX","ADX Wilder","Custom" }; //--- Set up properties before creation m_indicator_type.XSize(200); m_indicator_type.YSize(26); m_indicator_type.LabelYGap(4); m_indicator_type.ItemsTotal(SIZE); m_indicator_type.Font(m_base_font); m_indicator_type.FontSize(m_base_font_size); m_indicator_type.BackColor(m_background); m_indicator_type.GetButtonPointer().Font(m_base_font); m_indicator_type.GetButtonPointer().FontSize(m_base_font_size); m_indicator_type.GetButtonPointer().BackColor(clrWhite); m_indicator_type.GetButtonPointer().XGap(100); m_indicator_type.GetButtonPointer().XSize(100); m_indicator_type.GetListViewPointer().Font(m_base_font); m_indicator_type.GetListViewPointer().FontSize(m_base_font_size); m_indicator_type.GetListViewPointer().ItemYSize(25); m_indicator_type.GetListViewPointer().YSize(200); //--- Save the item values in the combobox list view for(int i=0; i<SIZE; i++) m_indicator_type.SetValue(i,pattern_names[i]); //--- Get the list view pointer CListView *lv=m_indicator_type.GetListViewPointer(); //--- Set the list view properties lv.LightsHover(true); m_indicator_type.SelectItem(1); //--- Create the control if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,m_indicator_type); return(true); }
Betrachten wir nun, was sich im Vergleich zur vorherigen Version geändert hat. Zunächst haben wir Size-Makrosubstitution hinzugefügt, was die Anzahl der Elemente in der Dropdown-Liste bedeutet. Auf diese Weise können wir die Listenlänge von einer einzigen Stelle aus ändern, ohne dass wir in allen Codeteilen Ersetzungen vornehmen müssen. Fügen wir dann am Ende ein neues Listenelement hinzu: Custom. Die Änderungen sind in Abbildung 1 unten dargestellt.
Abb.1 Hinzufügen eines Elements zur Auswahl eines nutzerdefinierten Indikators.
Fügen wir nun neue Oberflächenelemente für die Einrichtung und Verwendung des Indikators hinzu. Wir müssen Änderungen in Übereinstimmung mit den iCustom() Funktionsargumenten vornehmen, um den berechneten Teil Ihres eigenen Indikators zu verwenden. Dazu gehören der Symbolname, der Punkt, der Pfad zur kompilierten *.ex5-Indikatordatei und eine durch Komma getrennte Liste von Indikatorparametern.
int iCustom( string symbol, // symbol name ENUM_TIMEFRAMES period, // period string name // folder/custom indicator_name ... // the list of indicator input parameters );
Der Symbolname und der Zeitrahmen werden durch die Werte ersetzt, die in den ersten beiden Schritten der anfänglichen Anwendungseinrichtung ausgewählt wurden. Die Nutzer müssen jedoch den Indikatorpfad und die Liste der Parameter selbst festlegen. Zu diesem Zweck müssen zwei zusätzliche Felder hinzugefügt werden. Fügen wir zwei neue Variablen und eine Methode zur Basisklasse CProgramm hinzu:
CTextEdit m_custom_path; CTextEdit m_custom_param; bool CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text);
Da die Methode im Fenster zum Erstellen/Bearbeiten von Handelssignalen angewendet wird, implementieren wir sie in der Datei SetWindow.mqh:
//+------------------------------------------------------------------+ //| Input field for a custom indicator | //+------------------------------------------------------------------+ bool CProgram::CreateCustomEdit(CTextEdit &text_edit,const int x_gap,const int y_gap,const string default_text) { //--- Save the pointer to the main control text_edit.MainPointer(m_set_window); //--- Properties text_edit.XSize(100); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.GetTextBoxPointer().AutoSelectionMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(325); text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver); text_edit.GetTextBoxPointer().DefaultText(default_text); text_edit.GetTextBoxPointer().BorderColor(clrBlack); //--- Create the control if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.IsLocked(true); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,text_edit); return(true); }
Erstellen wir zwei Eingabefelder mit der Methode CreateCustomEdit(). Suchen Sie in derselben Datei im Methodenrumpf CreateSetWindow() den Abschnitt Selected indicator settings und fügen Sie den folgenden Code hinzu:
if(!CreateCustomEdit(m_custom_path,240,22+10+2*(25+10),"Enter the indicator path")) return(false); if(!CreateCustomEdit(m_custom_param,240,22+10+3*(25+10),"Enter indicator parameters separated by commas")) return(false);
Infolgedessen erscheinen im Einstellungsfenster zwei Eingabefelder, wie in Abbildung 2 dargestellt.
Abb. 2 Hinzufügen von Eingabefeldern für benutzerdefinierte Indikatoreinstellungen.
Sie sind in diesem Entwicklungsstadium inaktiv. Dies liegt daran, dass ihre Verfügbarkeit strikt vom gewählten Indikatortyp abhängt, d.h. sie sind nur verfügbar, wenn Custom in der Dropdown-Liste ausgewählt ist. Um diese Aufgabe umzusetzen, überarbeiten wir die Methode RebuildParameters(). Zunächst gehen wir aber zum Abschnitt Ereignis zur Auswahl eines Elements aus der Dropdown-Liste in der Methode OnEvent() und fügen eine Prüfung für das Ereignis der gewünschten Liste mit der Auswahl des Indikatortyps hinzu.
//--- Selecting an item in the combobox drop-down list if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { //--- Indicator type if(lparam==m_indicator_type.Id()) RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex()); }
Ändern wir nun die Methode RebuildParameters() so, dass bei Auswahl jedes der verfügbaren Indikatoren die entsprechenden Einstellungen angezeigt werden. Außerdem würde dies für den nutzerdefinierten Indikator die Eingabefelder für Pfade und Parameter aktiv machen.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::RebuildParameters(int index) { switch(index) { case 0: m_period_edit.LabelText("ATR Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 1: m_period_edit.LabelText("CCI Period"); m_applied_price.Show(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 2: m_period_edit.LabelText("DeMarker Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 3: m_period_edit.LabelText("Force Index Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 4: m_period_edit.LabelText("WPR Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 5: m_period_edit.LabelText("RSI Period"); m_applied_price.Show(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 6: m_period_edit.LabelText("Momentum Period"); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 7: m_period_edit.LabelText("ADX Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 8: m_period_edit.LabelText("ADXW Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; case 9: m_period_edit.LabelText("Buffer Number"); m_applied_price.Hide(); m_custom_param.IsLocked(false); m_custom_path.IsLocked(false); break; default: m_period_edit.LabelText("Ind Period"); m_applied_price.Hide(); m_custom_param.IsLocked(true); m_custom_path.IsLocked(true); break; } m_period_edit.Update(true); }
Jetzt sollte ein Kompilieren folgendes Ergebnis zeigen:
Abb. 3 Hinzufügen von Eingabefeldern für den nutzerdefinierten Indikator.
Der nächste Schritt ist die Ergänzung der Schaltfläche Ereignis des Signals hinzufügen. Wenn er gedrückt wird, setzen Sie die Auswahl und die Einstellungen des Indikators auf Standard.
//--- Add Signal button click event if(lparam==m_add_signal.Id()) { if(m_total_signals>4) { MessageBox("Maximum number of signals is 5","Signal Monitor"); return; } m_set_window.OpenWindow(); RebuildParameters(1); m_number_signal=-1; RebuildTimeframes(); m_new_signal.LabelText("Add"); m_new_signal.Update(true); m_indicator_type.SelectItem(1); m_indicator_type.GetButtonPointer().Update(true); }
Bevor wir neue Bedienelemente (Eingabefelder) an einen bestehenden Algorithmus anpassen, der eine Reihe von Signaleinstellungen speichert, sollten wir das System der Suchregeln für Handelssignale erweitern. Dies wird die Hinzufügung neuer Schnittstellenelemente bewirken. Es ist jedoch nicht sinnvoll, den Algorithmus zur Speicherung des Satzes jedes Mal zu ändern, wenn wir das System der Einstellungen erweitern. Eine logischere Lösung besteht darin, alle neuen Einstellungs- und Steuerelemente hinzuzufügen und dann die Methode zur Speicherung des Satzes von Parametern für die Signalsuche zu ändern.
Erweiterung des Systems der Suchregeln für Handelssignale
Gegenwärtig kann der Handelsmonitor Signale erzeugen, die auf Ungleichheit beruhen. Damit ist ein Zustand größer, kleiner oder gleich einer bestimmten Zahl gemeint. Eine solche Wahl spiegelt jedoch nicht immer genau die gewünschten Signale wider. Beispielsweise sind Oszillatorindikatoren manchmal für die Verwendung mit einem bestimmten Wertebereich besser geeignet. Genau das werden wir jetzt implementieren. Zunächst ist es notwendig, einen Schalter zwischen dem bisherigen und dem neuen Regelaufbausystem hinzuzufügen. Es sollte eine neue Dropdown-Liste mit zwei Regel-Setup-Typen hinzugefügt werden: Compare und Interval.
Wir gehen zur Basisklasse CProgramm und fügen eine neue Variable hinzu, eine Instanz der Klasse CСombobox und erstellen die Methode, die das UI-Element implementiert:
CComboBox m_rule_interval; bool CreateRuleInterval(const int x_gap,const int y_gap);
Die Methodenimplementierung sollte der Datei SetWindow.mqh hinzugefügt werden, da diese Dropdown-Liste zum Einstellungsfenster gehört.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleInterval(const int x_gap,const int y_gap) { //--- Pass the object to the panel m_rule_interval.MainPointer(m_set_window); //--- Array of the item values in the list view string pattern_names[2]= { "Compare","Interval", }; //--- Set up properties before creation m_rule_interval.XSize(160); m_rule_interval.YSize(26); m_rule_interval.LabelYGap(4); m_rule_interval.ItemsTotal(2); m_rule_interval.Font(m_base_font); m_rule_interval.FontSize(m_base_font_size); m_rule_interval.BackColor(m_background); m_rule_interval.GetButtonPointer().Font(m_base_font); m_rule_interval.GetButtonPointer().FontSize(m_base_font_size); m_rule_interval.GetButtonPointer().BackColor(clrWhite); m_rule_interval.GetButtonPointer().XGap(90); m_rule_interval.GetButtonPointer().XSize(80); m_rule_interval.GetListViewPointer().Font(m_base_font); m_rule_interval.GetListViewPointer().FontSize(m_base_font_size); m_rule_interval.GetListViewPointer().ItemYSize(26); //--- Save the item values in the combobox list view for(int i=0; i<2; i++) m_rule_interval.SetValue(i,pattern_names[i]); //--- Get the list view pointer CListView *lv=m_rule_interval.GetListViewPointer(); //--- Set the list view properties lv.LightsHover(true); m_rule_interval.SelectItem(0); //--- Create the control if(!m_rule_interval.CreateComboBox("Rule",x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,m_rule_interval); return(true); }
Die neue Intervallregel sollte eine untere und eine obere Grenze haben, fügen Sie also ein zusätzliches Feld für die Eingabe eines numerischen Wertes hinzu. Das vorherige Feld wird für den oberen Grenzwert verwendet, das neue Feld wird für den unteren verwendet. Es ist auch notwendig, die Möglichkeit vorzusehen, negative Werte für Indikatoren wie z.B. WPR anzugeben. In diesem Fall werden der obere und der untere Grenzwert vertauscht. Um zu vermeiden, dass für die Implementierung eines Eingabefeldes für eine untere Periode eine separate Methode erstellt werden muss, modifizieren Sie einfach die aktuelle Variable, die für das bestehende Eingabefeld verantwortlich ist, und die Methode CreateRule(). Die Variable wird dann zu einem Array:
CTextEdit m_rule_value[2];
Fügen wir der Methode noch ein neues Argument hinzu, das eine Referenz auf die Instanz der Klasse CTextEdit erhält.
bool CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap);
Ändern wir die Methodenimplementierung entsprechend.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRuleValue(CTextEdit &text_edit,const int x_gap,const int y_gap) { //--- Save the pointer to the main control text_edit.MainPointer(m_set_window); //--- Properties text_edit.XSize(80); text_edit.YSize(24); text_edit.GetTextBoxPointer().XGap(1); text_edit.LabelColor(C'0,100,255'); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(999); text_edit.StepValue(0.1); text_edit.MinValue(-999); text_edit.SetDigits(3); text_edit.SpinEditMode(true); //--- Create the control if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(5)); text_edit.GetTextBoxPointer().AutoSelectionMode(true); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,text_edit); return(true); }
Ändern wir auch einige der Werte der bestehenden Methode CreateRule():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateRule(const int x_gap,const int y_gap) { //--- Pass the object to the panel m_rule_type.MainPointer(m_set_window); //--- Array of the item values in the list view string pattern_names[5]= { ">",">=","==","<","<=" }; //--- Set up properties before creation m_rule_type.XSize(80); m_rule_type.YSize(26); m_rule_type.LabelYGap(4); m_rule_type.ItemsTotal(5); m_rule_type.Font(m_base_font); m_rule_type.FontSize(m_base_font_size); m_rule_type.BackColor(m_background); m_rule_type.GetButtonPointer().Font(m_base_font); m_rule_type.GetButtonPointer().FontSize(m_base_font_size); m_rule_type.GetButtonPointer().BackColor(clrWhite); m_rule_type.GetButtonPointer().XGap(1); m_rule_type.GetButtonPointer().XSize(80); m_rule_type.GetListViewPointer().Font(m_base_font); m_rule_type.GetListViewPointer().FontSize(m_base_font_size); m_rule_type.GetListViewPointer().ItemYSize(26); //--- Save the item values in the combobox list view for(int i=0; i<5; i++) m_rule_type.SetValue(i,pattern_names[i]); //--- Get the list view pointer CListView *lv=m_rule_type.GetListViewPointer(); //--- Set the list view properties lv.LightsHover(true); m_rule_type.SelectItem(0); //--- Create the control if(!m_rule_type.CreateComboBox("",x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(1,m_rule_type); return(true); }
Suchen wir nun den Abschnitt Condition Settings in der Methode CreateSetWindow(), und ändern den Code wie folgt:
//--- Condition settings if(!CreateRuleValue(m_rule_value[0],200,22+10+5*(25+10))) return(false); if(!CreateRuleValue(m_rule_value[1],300,22+10+5*(25+10))) return(false); if(!CreateRule(200,22+10+5*(25+10))) return(false); if(!CreateRuleInterval(10,22+10+5*(25+10))) return(false);
Diese Änderung ermöglicht es Ihnen, die Position der vorhandenen Schnittstellenelemente neu zu konfigurieren und neue hinzuzufügen. Das Ergebnis sollte wie in Abb. 4 dargestellt sein. Allerdings funktioniert nichts wie bisher, wenn wir versuchen, den Modus der Regeln von Compare auf Interval umzuschalten. Lassen Sie uns das beheben.
Abb. 4 Hinzufügen der Modusauswahl für die Regeln der Signalsuche.
Öffnen Sie dazu die Methode OnEvent() und suchen Sie in der Dropdown-Liste den Abschnitt, der für ein Elementauswahl-Ereignis verantwortlich ist, und fügen Sie einen Code hinzu, der es ermöglicht, je nach ausgewähltem Modus korrekte Schnittstellenelemente anzuzeigen.
//--- Selecting an item in the combobox drop-down list if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { ... //--- Rule type if(lparam==m_rule_interval.Id()) { switch(m_rule_interval.GetListViewPointer().SelectedItemIndex()) { case 0: m_rule_value[0].Hide(); m_rule_type.Show(); break; case 1: m_rule_value[0].Show(); m_rule_type.Hide(); break; default: break; } } }
Als Nächstes verschieben wir einige der Ereignisse im Zusammenhang mit dem Laden der Schnittstelle in einen separaten Abschnitt der Methode OnEvent(). Dazu erstellen wir das Ereignis der Vollendung der Schnittstellenerstellung und verschieben den Code von der Methode CreateGUI() zu diesem Ereignis. Der folgende Code wird in CreateGUI belassen:
//+------------------------------------------------------------------+ //| Creates the graphical interface of the program | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Loading the language ChangeLanguage(); //--- Step 1-3. Symbol selection window. if(!CreateStepWindow(m_lang[0])) return(false); //--- if(!CreateSetWindow(m_lang[17])) return(false); //--- Creating form 2 for the color picker if(!CreateColorWindow("Color Picker")) return(false); //--- Finishing the creation of GUI CWndEvents::CompletedGUI(); return(true); }
Der neue Abschnitt wird wie folgt aussehen:
// --- GUI creation completion if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { m_back_button.Hide(); m_add_signal.Hide(); m_signal_header.Hide(); m_label_button[1].IsPressed(true); m_label_button[1].Update(true); for(int i=0; i<5; i++) m_signal_editor[i].Hide(); m_rule_value[0].Hide(); }
Achten Sie auf die neue Aktion beim Laden der Anwendung — Ausblenden des neu erstellten Feldes zur Eingabe der unteren Intervallgrenze.
Nachdem wir die neuen UI-Elemente und Parameter erstellt haben, können wir mit der Modifizierung des Ladealgorithmus und dem Speichern von Sätzen von Handelssignaleinstellungen fortfahren. Gehen wir zum Methodenrumpf SaveSignalSet() und passen ihn an die letzten Änderungen an.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::SaveSignalSet(int index) { //--- int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Не удалось создать файл конфигурации","Монитор сигналов"); else MessageBox("Failed to create configuration file","Signal Monitor"); return(false); } if(index>4) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Максимальное число сигналов не должно быть больше 5","Монитор сигналов"); else MessageBox("Maximum number of signals is 5","Signal Monitor"); return(false); } //--- Save the selection //--- Indicator type m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex(); //--- Indicator period if(m_signal_set[index].ind_type!=9) { m_signal_set[index].ind_period=(int)m_period_edit.GetValue(); //--- Type of applied price m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex(); } else { string path=m_custom_path.GetValue(); string param=m_custom_param.GetValue(); if(path=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Введите путь к индикатору","Монитор сигналов"); else MessageBox("Enter the indicator path","Signal Monitor"); FileClose(h); return(false); } if(param=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Введите параметры индикатора через запятую","Монитор сигналов"); else MessageBox("Enter indicator parameters separated by commas","Signal Monitor"); FileClose(h); return(false); } StringToCharArray(path,m_signal_set[index].custom_path); StringToCharArray(param,m_signal_set[index].custom_val); m_signal_set[index].ind_period=(int)m_period_edit.GetValue(); } //--- Rule type m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex(); //--- Comparison type m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex(); //--- Rule value m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue(); m_signal_set[index].rule_value2=(double)m_rule_value[1].GetValue(); //--- Text label display type m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1; //--- Save the value of the text field for the second type if(m_label_button[1].IsPressed()) StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value); //--- Color of the text label m_signal_set[index].label_color=m_color_button[0].CurrentColor(); //--- Background color if(m_set_param[0].IsPressed()) m_signal_set[index].back_color=m_color_button[1].CurrentColor(); else m_signal_set[index].back_color=clrNONE; //--- Border color if(m_set_param[1].IsPressed()) m_signal_set[index].border_color=m_color_button[2].CurrentColor(); else m_signal_set[index].border_color=clrNONE; //--- Tooltip value m_signal_set[index].tooltip=m_set_param[2].IsPressed(); if(m_signal_set[index].tooltip) StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text); //--- Selected image m_signal_set[index].image=m_set_param[3].IsPressed(); if(m_signal_set[index].image) m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex(); //--- Selected timegrames int tf=0; for(int i=0; i<21; i++) { if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed()) { m_signal_set[index].timeframes[i]=true; StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf); tf++; } else m_signal_set[index].timeframes[i]=false; } //--- if(tf<1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Не выбран ни один таймфрейм","Монитор сигналов"); else MessageBox("No timeframes selected","Signal Monitor"); FileClose(h); return(false); } //--- FileWriteStruct(h,m_signal_set[index]); FileClose(h); Print("Configuration signal_"+string(index)+" has been successfully saved"); //--- return(true); }
Der obige Code enthält eine Vielzahl von Änderungen. Lassen Sie uns die wichtigsten Änderungen im Detail besprechen. Die erste ist die Prüfung, ob ein Standard- oder ein nutzerdefinierter Indikator ausgewählt ist. Wenn Sie Ihr eigenes Kennzeichen wählen, schreiben wir den Algorithmus der Speicherung des Pfades zum Kennzeichen und seiner Parameter sowie den Wert aus dem Periodeneingabefeld, aber wenn Sie Ihr eigenes Kennzeichen wählen, dient dieses Feld als Nummer des Indikatorpuffers.
Wir haben die Anzahl der gespeicherten Parameter geändert. Dementsprechend müssen wir die SIGNAL-Struktur ändern, über die alles in einer Binärdatei gespeichert wird. Hinzufügen der neuen Variablen:
struct SIGNAL { int ind_type; int ind_period; int app_price; int rule_int; int rule_type; double rule_value1; double rule_value2; int label_type; uchar label_value[10]; color label_color; color back_color; color border_color; bool tooltip; uchar tooltip_text[100]; bool image; int img_index; bool timeframes[21]; TFNAME tf_name[21]; uchar custom_path[100]; uchar custom_val[100]; };
Das Eingabefeld für den Schwellenwert ist jetzt für den Vergleich auf rule_value geändert, und es ist für die Ober- und Untergrenzen im Intervallmodus auf rule_value 1 und rule_value 2 geändert. Die Variablen custom_path und custom_val wurden hinzugefügt, um Daten über den benutzerdefinierten Indikatorpfad und seine Parameter zu speichern. Außerdem wurde die Methode zum Laden des Satzes von Handelssignalparametern aus einer Datei geändert: LoadSignalSet():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::LoadSignalSet(int index) { int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuration not found","Signal Monitor"); return(false); } ZeroMemory(m_signal_set[index]); FileReadStruct(h,m_signal_set[index]); //--- Loading indicator type m_indicator_type.SelectItem(m_signal_set[index].ind_type); RebuildParameters(m_signal_set[index].ind_type); m_indicator_type.GetButtonPointer().Update(true); if(m_signal_set[index].ind_type!=9) { //--- Loading indicator period m_period_edit.SetValue((string)m_signal_set[index].ind_period); m_period_edit.GetTextBoxPointer().Update(true); //--- Loading applied price if(!m_applied_price.IsLocked()) { m_applied_price.SelectItem(m_signal_set[index].app_price); m_applied_price.GetButtonPointer().Update(true); } } else { m_period_edit.SetValue((string)m_signal_set[index].ind_period); m_custom_path.SetValue(CharArrayToString(m_signal_set[index].custom_path)); m_custom_param.SetValue(CharArrayToString(m_signal_set[index].custom_val)); m_custom_path.GetTextBoxPointer().Update(true); m_custom_param.GetTextBoxPointer().Update(true); } //--- Loading signal rule m_rule_interval.SelectItem(m_signal_set[index].rule_int); m_rule_interval.GetButtonPointer().Update(true); m_rule_type.SelectItem(m_signal_set[index].rule_type); m_rule_type.GetButtonPointer().Update(true); m_rule_value[0].SetValue((string)m_signal_set[index].rule_value1); m_rule_value[0].GetTextBoxPointer().Update(true); m_rule_value[1].SetValue((string)m_signal_set[index].rule_value2); m_rule_value[1].GetTextBoxPointer().Update(true); //--- Loading a text label if(m_signal_set[index].label_type==0) { m_label_button[0].IsPressed(true); m_label_button[0].Update(true); m_label_button[1].IsPressed(false); m_label_button[1].Update(true); m_text_box.IsLocked(true); } else { m_label_button[0].IsPressed(false); m_label_button[0].Update(true); m_label_button[1].IsPressed(true); m_label_button[1].Update(true); m_text_box.IsLocked(false); m_text_box.ClearTextBox(); m_text_box.AddText(0,CharArrayToString(m_signal_set[index].label_value)); m_text_box.Update(true); } //--- Loading the color of the text label m_color_button[0].CurrentColor(m_signal_set[index].label_color); m_color_button[0].Update(true); //--- Loading the background color if(m_signal_set[index].back_color==clrNONE) { m_set_param[0].IsPressed(false); m_set_param[0].Update(true); m_color_button[1].IsLocked(true); m_color_button[1].GetButtonPointer().Update(true); } else { m_set_param[0].IsPressed(true); m_set_param[0].Update(true); m_color_button[1].IsLocked(false); m_color_button[1].CurrentColor(m_signal_set[index].back_color); m_color_button[1].GetButtonPointer().Update(true); } //--- Loading the border color if(m_signal_set[index].border_color==clrNONE) { m_set_param[1].IsPressed(false); m_set_param[1].Update(true); m_color_button[2].IsLocked(true); m_color_button[2].GetButtonPointer().Update(true); } else { m_set_param[1].IsPressed(true); m_set_param[1].Update(true); m_color_button[2].IsLocked(false); m_color_button[2].CurrentColor(m_signal_set[index].border_color); m_color_button[2].GetButtonPointer().Update(true); } //--- Loading the tooltip value if(!m_signal_set[index].tooltip) { m_set_param[2].IsPressed(false); m_set_param[2].Update(true); m_tooltip_text.IsLocked(true); m_tooltip_text.Update(true); } else { m_set_param[2].IsPressed(true); m_set_param[2].Update(true); m_tooltip_text.IsLocked(false); m_tooltip_text.ClearTextBox(); m_tooltip_text.AddText(0,CharArrayToString(m_signal_set[index].tooltip_text)); m_tooltip_text.Update(true); } //--- Loading the image if(!m_signal_set[index].image) { m_set_param[3].IsPressed(false); m_set_param[3].Update(true); m_pictures_slider.IsLocked(true); m_pictures_slider.GetRadioButtonsPointer().Update(true); } else { m_set_param[3].IsPressed(true); m_set_param[3].Update(true); m_pictures_slider.IsLocked(false); m_pictures_slider.GetRadioButtonsPointer().SelectButton(m_signal_set[index].img_index); m_pictures_slider.GetRadioButtonsPointer().Update(true); } //--- Loading selected timeframes for(int i=0; i<21; i++) { if(!m_tf_button[i].IsLocked()) { m_tf_button[i].IsPressed(m_signal_set[index].timeframes[i]); m_tf_button[i].Update(true); } } //--- FileClose(h); return(true); }
Es ist eine Prüfung implementiert, ob ein Standardindikator aus einer Datei ausgewählt wurde oder ein nutzerdefinierter Indikator verwendet wird. Entsprechend werden die erforderlichen Daten zur weiteren Bearbeitung in die Oberfläche des Einstellungsfensters geladen.
Das Speichern und Laden eines Satzes von Handelssignalparametern ist vorbereitet. Verfeinern wir nun den Signalsuchalgorithmus. Öffnen Sie die Methode GetSignal() und suchen Sie den Abschnitt Check for conditions. Ersetzen Sie den Abschnitt wie folgt:
//--- Check the condition int s=0; if(signal_set.rule_int==0) { double r_value=signal_set.rule_value2; double c_value=val[0]; m_ind_value=c_value; switch(signal_set.rule_type) { case 0: if(c_value>r_value) s=1; break; case 1: if(c_value>=r_value) s=1; break; case 2: if(c_value==r_value) s=1; break; case 3: if(c_value<r_value) s=1; break; case 4: if(c_value<=r_value) s=1; break; default: s=0; break; } } else if(signal_set.rule_int==1) { double r_value_min=signal_set.rule_value1; double r_value_max=signal_set.rule_value2; double c_value=val[0]; m_ind_value=c_value; if(c_value>=r_value_min && c_value<=r_value_max) s=1; }
Nehmen Sie auch die hinzugefügten Indikatoren in den Abschnitt Abrufen des Handles des ausgewählten Indikators:
//--- Get the handle of the selected indicator string str[],name; double arr[]; switch(signal_set.ind_type) { case 0: h=iATR(sy,tf,signal_set.ind_period); break; case 1: h=iCCI(sy,tf,signal_set.ind_period,app_price); break; case 2: h=iDeMarker(sy,tf,signal_set.ind_period); break; case 3: h=iForce(sy,tf,signal_set.ind_period,MODE_SMA,VOLUME_TICK); break; case 4: h=iWPR(sy,tf,signal_set.ind_period); break; case 5: h=iRSI(sy,tf,signal_set.ind_period,app_price); break; case 6: h=iMomentum(sy,tf,signal_set.ind_period,app_price); break; case 7: h=iADX(sy,tf,signal_set.ind_period); break; case 8: h=iADXWilder(sy,tf,signal_set.ind_period); break; case 9: StringSplit(m_custom_param.GetValue(),StringGetCharacter(",",0),str); ArrayResize(arr,ArraySize(str)); for(int i=0; i<ArraySize(str); i++) arr[i]=StringToDouble(str[i]); name=m_custom_path.GetValue(); h=GetCustomValue(tf,name,arr); break; default: break; }
Dieser Block enthält nun die Prüfung für den Suchmodus, Comparison oder Interval. Die Prüfung der Bedingungen wird entsprechend angewendet.
Umwandlung der Symbolliste in eine Tabelle
Wenn es nicht viele Symbole auf einem Handelskonto gibt, kann die Möglichkeit, sie durch den Namen mit einem Kontrollkästchen daneben implementiert auszuwählen, völlig ausreichend sein. Wenn man jedoch mit Hunderten von Symbolen arbeitet, nimmt die Höhe des Anwendungsfensters stark zu (da es entsprechend der Anzahl der Zeilen mit Symbolen skaliert wird). Aus diesem Grund wurde diese Ansicht durch eine tabellarische Form ersetzt. Außerdem werden bei zu vielen Symbolen einige von ihnen ausgeblendet und eine Bildlaufleiste auf der rechten Seite hinzugefügt. Wir benötigen jedoch weiterhin Kontrollkästchen für die Auswahl von Arbeitszeitrahmen. Daher müssen wir mehrere Probleme lösen:
- Erstellen einer Tabelle, um die gleichen Symbolnamen mit Kontrollkästchen anzuzeigen.
- Vorhandene Kontrollkästchen so vereinfachen, dass nur die Arbeitszeiten ausgewählt werden.
- Anpassen aller UI-Änderungen an den bestehenden Algorithmus zur Auswahl von Symbolen und Arbeitszeiten für die weitere Suche nach Handelssignalen.
Lassen Sie uns zunächst die Anzeige der alten Kontrollkästchen entfernen. Dazu blenden wir sie im Ereignis GUI-Erstellung abschließen aus. Nun können wir wissen, dass die Anzahl der Kontrollkästchen konstant ist: 21, was nach Möglichkeit der Gesamtzahl der Zeitrahmen im Terminal entspricht. Wir wandeln daher das dynamische Array m_checkbox[] in ein statisches Array mit einer Größe von 21 um.
//--- Hide timeframe checkboxes for(int i=0; i<21; i++) m_checkbox[i].Hide();
Auch die Methode zur Erstellung von Kontrollkästchen muss aufgrund ihres eindeutigen Zwecks angepasst werden. Wir gehen zum Code der Methode CreateStepWindow() und ersetzen den Abschnitt Checkboxes wie folgt:
//--- Checkboxes int k=0; string timeframe_names[21]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30", "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; for(int j=0; j<=3; j++) { for(int i=0; i<7; i++) { if(k<21) if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,timeframe_names[k])) return(false); k++; } }
Entfernen wir auch das Fenster zur Berechnung der Fensterhöhe (es war notwendig, um die Höhe entsprechend der Anzahl der Symbole in der Market Watch zu berechnen).
m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);
Das Fenster wird statisch:
m_step_window.YSize(500);
Jetzt müssen wir das Grundobjekt einer Tabelle erstellen, die dann mit Daten aus der Market Watch gefüllt wird. Wir erstellen die Klasseninstanz CTable und die Tabellenimplementierungsmethode.
//--- Rendered table CTable m_table; bool CreateTable(const int x_gap,const int y_gap);
Implementation des Hauptfensters in der Datei StepWindow.mqh:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CProgram::CreateTable(const int x_gap,const int y_gap) { #define COLUMNS1_TOTAL 7 #define ROWS1_TOTAL int(MathCeil(m_all_symbols/7)) //--- Save the pointer to the main control m_table.MainPointer(m_step_window); //--- Array of column widths int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,80); //--- Array of text offset along the X axis in the columns int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,25); //--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_LEFT); //--- Array of column image offsets along the X axis int image_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_x_offset,5); //--- Array of column image offsets along the Y axis int image_y_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_y_offset,4); //--- Properties m_table.XSize(560); m_table.YSize(190); m_table.Font(m_base_font); m_table.FontSize(m_base_font_size); m_table.CellYSize(20); m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ImageXOffset(image_x_offset); m_table.ImageYOffset(image_y_offset); m_table.LabelXGap(5); m_table.LabelYGap(4); m_table.IconXGap(7); m_table.IconYGap(4); m_table.MinColumnWidth(0); m_table.LightsHover(true); m_table.SelectableRow(false); m_table.IsWithoutDeselect(false); m_table.ColumnResizeMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoXResizeRightOffset(10); m_table.AutoYResizeMode(true); m_table.AutoYResizeBottomOffset(50); //--- Populate the table with data InitializingTable(); //--- Create the control if(!m_table.CreateTable(x_gap,y_gap)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(0,m_table); return(true); }
Bevor diese Methode zur Erstellung einer Tabelle verwendet wird, sollten folgende Daten eingetragen werden: die Liste aller Symbole bildet die Market Watch für das aktuelle Handelskonto. Dies wird durch die Methode InitializingTable() durchgeführt: fügen Sie sie in den privaten Bereich der Basisklasse ein:
//+------------------------------------------------------------------+ //| Initialize the table | //+------------------------------------------------------------------+ void CProgram::InitializingTable(void) { //--- Array of icons 1 string image_array1[2]= { "Images\\EasyAndFastGUI\\Controls\\checkbox_off.bmp", "Images\\EasyAndFastGUI\\Controls\\checkbox_on_g.bmp" }; //--- int k=0; for(int c=0; c<COLUMNS1_TOTAL; c++) { //--- for(int r=0; r<ROWS1_TOTAL; r++) { if(k<m_all_symbols) { //--- Set the cell type to Checkbox m_table.CellType(c,r,CELL_CHECKBOX); m_table.SetImages(c,r,image_array1); //--- Set the text m_table.SetValue(c,r,SymbolName(k,false)); } k++; } } }
Verwenden wir nun die obigen Vorbereitungen im Methodenkörper von CreateStepWindow(), um die Tabelle zu erstellen und zu füllen. Als Ergebnis erhalten wir eine Liste aller Symbole des Market Watch (Abb.5), die ebenfalls auf dem Checkbox-Auswahltyp basiert, nun aber in tabellarischer Form dargestellt wird.
Abb. 5 Ergebnis der Konvertierung der Liste der Symbole in eine tabellarische Form.
Der nächste Schritt besteht darin, die neu erstellte Tabelle mit den Interaktionen zu verknüpfen, die für den vorherigen Satz von Kontrollkästchen verfügbar waren. Die folgenden Möglichkeiten sollten vorgesehen werden:
- Auswahl vordefinierter Sätze: ALL, Major und Crosses.
- Speichern und Laden von Vorlagen eines nutzerdefinierten Symbolsatzes.
Um die erste Möglichkeit zu implementieren, ersetzen Sie Abschnitte mit Vorlagennamen in OnEvent() durch den folgenden Code:
//--- All if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed()) { m_currency_set[1].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[1].Update(true); m_currency_set[2].Update(true); //--- int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,1); k++; } } m_table.Update(true); } //--- Majors else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[2].Update(true); //--- string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"}; //--- Clear the selection int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } //--- k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { for(int j=0; j<4; j++) { if(m_table.GetValue(c,r)==pairs[j]) m_table.ChangeImage(c,r,1); } } k++; } } m_table.Update(true); } //--- Crosses else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[1].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[1].Update(true); //--- string pairs[20]= { "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY", "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF" }; //--- Clear the selection int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } //--- k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { for(int j=0; j<20; j++) { if(m_table.GetValue(c,r)==pairs[j]) m_table.ChangeImage(c,r,1); } } k++; } } m_table.Update(true); } //--- if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed()) || (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed()) || (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed()) ) { //--- Clear the selection int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_table.ChangeImage(c,r,0); k++; } } m_table.Update(true); }
Das Speichern und Laden ausgewählter Symbolsätze wurde mit Hilfe von Methoden durchgeführt. Wir verwendeten die Methoden SaveSymbolSet() und LoadSymbolSet(), um die ausgewählten Symbolsätze zu speichern bzw. zu laden. Hier müssen wir den Codeteil ändern, in dem Daten aus einem Kontrollkästchen entnommen werden, da die Daten aus einer neu erstellten Tabelle entnommen werden sollen. Dementsprechend sollten die Daten in dieselbe Tabelle geladen werden.
//+------------------------------------------------------------------+ //| Save template to a file | //+------------------------------------------------------------------+ bool CProgram::SaveSymbolSet(string file_name) { if(file_name=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Выберите имя шаблона для записи","Монитор сигналов"); else MessageBox("Choose a name for the template to save","Signal Monitor"); return(false); } int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Не удалось создать файл конфигурации","Монитор сигналов"); else MessageBox("Failed to create configuration file","Signal Mo nitor"); return(false); } else MessageBox("The "+file_name+" configuration has been successfully saved","Signal Monitor"); //--- Save symbol selection int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) m_save.tf[k]=m_table.SelectedImageIndex(c,r)>0?true:false; k++; } } //--- FileWriteStruct(h,m_save); FileClose(h); //--- return(true); } //+------------------------------------------------------------------+ //| Load data to a panel | //+------------------------------------------------------------------+ bool CProgram::LoadSymbolSet(string file_name) { if(file_name=="") { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Выберите имя шаблона для загрузки","Монитор сигналов"); else MessageBox("Choose a name for the template to load","Signal Monitor"); return(false); } int h=FileOpen("Signal Monitor\\"+file_name+".bin",FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) { MessageBox("Configuration "+file_name+" not found","Signal Monitor"); return(false); } ZeroMemory(m_save); FileReadStruct(h,m_save); //--- Load symbol selection int k=0; for(int c=0; c<7; c++) { //--- for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(k<m_all_symbols) { if(m_save.tf[k]) m_table.ChangeImage(c,r,1); else m_table.ChangeImage(c,r,0); } k++; } } m_table.Update(true); //--- FileClose(h); //--- return(true); }
Markieren wir nun den Block, in dem Daten gesammelt und in einer Struktur gespeichert werden, die in einer Datei gespeichert wird. Außerdem werden hier Daten aus einer Datei in eine Struktur geladen und Daten aus dieser in die Tabelle eingefügt.
Das visuelle Ergebnis aller Änderungen und Ergänzungen ist in Abbildung 6 dargestellt, aber der Hauptzweck bestand darin, eine komfortable Bedienung mit einer großen Anzahl von Symbolen zu ermöglichen, die nicht in das Fenster passten.
Abb.6 Das Ergebnis der Addition einer Tabelle und ihre Interaktion mit den UI-Elementen.
Außerdem müssen wir die Methoden der Übergänge zwischen Schritt 1 und Schritt 2 bearbeiten, da wir die Art und Weise, wie wir Informationen über ausgewählte Symbole erhalten, geändert haben. Übergänge zwischen Konfigurationsschritten werden über zwei Methoden durchgeführt, die geändert werden müssen. Die Methode Zu_Step1() sollte wie folgt geändert werden: Wenn wir von Schritt 2 zu Schritt 1 springen, sollte die Möglichkeit, Zeitrahmen auszuwählen, ausgeblendet und die Tabelle angezeigt werden.
//+------------------------------------------------------------------+ //| Go to Step 1 | //+------------------------------------------------------------------+ void CProgram::ToStep_1(void) { //--- Change header m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols"); m_step_window.Update(true); //--- Hide the Back button m_back_button.Hide(); //--- Show the table m_table.Show(); //--- Hide timeframes for(int i=0; i<21; i++) m_checkbox[i].Hide(); string names[3]= {"All","Majors","Crosses"}; //--- Change names of selection buttons for(int i=0; i<3; i++) { m_currency_set[i].IsPressed(false); m_currency_set[i].LabelText(names[i]); m_currency_set[i].Update(true); } //--- Show block for working with templates m_text_edit.Show(); m_load_button.Show(); m_save_button.Show(); //--- Set the current setup step m_current_step=1; }
Blenden Sie in der Methode To_Step2() die Tabelle aus, Zeitrahmenauswahl anzeigen und merken Sie sich die im ersten Schritt ausgewählten Symbole.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::ToStep_2(void) { //--- Check whether at least one symbol is selected int cnt=0; //--- for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) cnt++; } } //--- if(cnt<1) { if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian") MessageBox("Не выбран ни один символ!","Внимание"); else MessageBox("No symbols selected!","Warning"); return; } //--- Hide the table m_table.Hide(); //--- Display timeframes for(int i=0; i<21; i++) m_checkbox[i].Show(); //--- Count the number of selected symbols ArrayResize(m_symbols,cnt); cnt=0; //--- Remember the selected symbols in the array for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) { m_symbols[cnt]=m_table.GetValue(c,r); cnt++; } } } //--- Set selected symbols in Market Watch for(int c=0; c<7; c++) { for(int r=0; r<MathCeil(m_all_symbols/7); r++) { if(m_table.SelectedImageIndex(c,r)>0) SymbolSelect(m_table.GetValue(c,r),true); else SymbolSelect(m_table.GetValue(c,r),false); } } //--- if(m_current_step==3) { m_add_signal.Hide(); m_signal_header.Hide(); m_next_button.LabelText("Next"); m_next_button.Update(true); for(int i=0; i<5; i++) m_signal_editor[i].Hide(); ClearSaves(); } //--- Change header m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes"); m_step_window.Update(true); string names[3]= {"All","Junior","Senior"}; //--- Change names of selection buttons for(int i=0; i<3; i++) { m_currency_set[i].LabelText(names[i]); m_currency_set[i].IsPressed(false); if(m_current_step==3) m_currency_set[i].Show(); m_currency_set[i].Update(true); } //--- Hide block for working with templates m_text_edit.Hide(); m_load_button.Hide(); m_save_button.Hide(); //--- Show Back button m_back_button.Show(); //--- m_current_step=2; }
Jetzt müssen wir die Interaktion der Schaltflächen zur Auswahl der voreingestellten Zeitrahmensätze mit der Liste der Kontrollkästchen anpassen. Da die Liste jetzt konstant ist, sollten entsprechende Änderungen im Code vorgenommen werden:
//--- All if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed()) { m_currency_set[1].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[1].Update(true); m_currency_set[2].Update(true); //--- for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } //--- Junior Timeframes else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[2].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[2].Update(true); //--- string pairs[11]= { "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30" }; //--- Clear the selection for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<21; i++) { for(int j=0; j<11; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } //--- Senior Timeframes else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed()) { m_currency_set[0].IsPressed(false); m_currency_set[1].IsPressed(false); m_currency_set[0].Update(true); m_currency_set[1].Update(true); //--- string pairs[10]= { "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN" }; //--- Clear the selection for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } //--- for(int i=0; i<21; i++) { for(int j=0; j<10; j++) if(m_checkbox[i].LabelText()==pairs[j]) { m_checkbox[i].IsPressed(true); m_checkbox[i].Update(true); } } } //--- if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed()) || (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed()) || (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed()) ) { //--- Clear the selection for(int i=0; i<21; i++) { m_checkbox[i].IsPressed(false); m_checkbox[i].Update(true); } }
Schnelle Bearbeitung der Suchregeln im Monitor
Während der Signalüberwachung muss der Nutzer möglicherweise die Bedingungen eines zuvor erstellten Handelssignals ändern. Gegenwärtig kann dies durch einen Neustart der Anwendung und eine Neukonfiguration jedes Signals des Monitors zusätzlich zum erforderlichen Signal erfolgen. Diese Lösung ist umständlich. Lassen Sie uns daher die Möglichkeit anbieten, fertige Handelssignale vom Monitor selbst aus zu bearbeiten. Fügen wir der Oberfläche des Monitors eine Schaltfläche hinzu, die es ermöglicht, ein kleines Dialogfeld zu öffnen, wie in Abbildung 7 dargestellt. Das Kästchen wird eine Liste aller erstellten Handelssignale enthalten. Ein Klick auf ein Signal öffnet es zur Bearbeitung.
Abb. 7 Bearbeiten eines früher erstellten Signals vom Monitor aus.
Fahren wir mit der Implementierung fort. Um die Schaltfläche anzuzeigen, die das Fenster mit der Liste der Handelssignale öffnet, fügen Sie die folgende Eigenschaft im Methodenrumpf CreateStepWindow() hinzu:
m_step_window.TooltipsButtonIsUsed(true);
Und deaktivieren Sie sie dann im Ereignis GGUI creation completion - so wird die Schaltfläche beim ersten Schritt der Anwendungseinrichtung nicht angezeigt und wird erst angezeigt, nachdem alle Signale erstellt und der Monitor gestartet wurde:
// --- GUI creation completion if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { ... m_step_window.GetTooltipButtonPointer().Hide(); }
Aktivieren Sie sie beim Laden des Monitors.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CProgram::AutoResize(const int x_size,const int y_size) { ... m_step_window.GetTooltipButtonPointer().Show(); }
Erstellen wir nun ein neues Dialogfeld, in dem die Liste der erstellten Signale angezeigt wird. Wir erzeugen eine Variable in der Klasse CWindow und die Methode CreateFastEdit(), die die Fenstererzeugung implementiert, sowie die Methode CreateFastEditor() zur Erzeugung von Schaltflächen (die Signalbearbeitung wird durch Klicken auf diese Schaltflächen durchgeführt).
CWindow m_fast_edit; bool CreateFastEdit(const string caption_text); bool CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap);
Implementation dieser Methode
//+------------------------------------------------------------------+ //| Creates a window for creating and editing trading signals | //+------------------------------------------------------------------+ bool CProgram::CreateFastEdit(const string caption_text) { //--- Add the window pointer to the window array CWndContainer::AddWindow(m_fast_edit); //--- Properties m_fast_edit.XSize(180); m_fast_edit.YSize(280); //--- Coordinates int x=m_step_window.XGap()+m_step_window.XSize()+10; int y=m_step_window.YGap(); //--- m_fast_edit.CaptionHeight(22); m_fast_edit.IsMovable(true); m_fast_edit.CaptionColor(m_caption); m_fast_edit.CaptionColorLocked(m_caption); m_fast_edit.CaptionColorHover(m_caption); m_fast_edit.BackColor(m_background); m_fast_edit.FontSize(m_base_font_size); m_fast_edit.Font(m_base_font); m_fast_edit.WindowType(W_DIALOG); //--- Creating the form if(!m_fast_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- for(int i=0; i<5; i++) { if(!CreateFastEditor(m_fast_editor[i],"Signal_"+string(i),10,40*i+40)) return(false); } return(true); } //+------------------------------------------------------------------+ //| Creates a button with an image | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp" bool CProgram::CreateFastEditor(CButton &button,string text,const int x_gap,const int y_gap) { //--- color baseclr=C'70,180,70'; color pressed=C'70,170,70'; //--- Save the window pointer button.MainPointer(m_fast_edit); //--- Set up properties before creation button.XSize(110); button.YSize(30); button.Font(m_base_font); button.FontSize(m_base_font_size); button.IconXGap(3); button.IconYGap(7); button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_light.bmp"); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressed); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressed); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Create the control if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Add the element pointer to the base CWndContainer::AddToElementsArray(3,button); return(true); }
Aufruf der Methode CreateFastEdit() in der Methode CreateGUI().
//+------------------------------------------------------------------+ //| Creates the graphical interface of the program | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Step 1-3. Symbol selection window. if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols")) return(false); //--- if(!CreateSetWindow("Signal Monitor Edit Signal")) return(false); //--- Creating form 2 for the color picker if(!CreateColorWindow("Color Picker")) return(false); //--- Creating a quick edit form if(!CreateFastEdit("Fast Signal Editor")) return(false); //--- Finishing the creation of GUI CWndEvents::CompletedGUI(); return(true); }
Nun sollte ein Klick auf die Schaltfläche Einstellungen im Monitor die Dialogbox mit den Signalen öffnen. Fügen Sie dazu den folgenden Code in den Abschnitt Klick auf die Schaltfläche Ereignis der Methode Ereignis() ein:
//--- OPEN THE SETTING WINDOW if(lparam==m_step_window.GetTooltipButtonPointer().Id()) { //--- Coordinates int x=m_step_window.X()+m_step_window.XSize()+10; int y=m_step_window.Y(); m_fast_edit.X(x); m_fast_edit.Y(y); m_fast_edit.OpenWindow(); }
Das folgende Ergebnis erhalten Sie, wenn Sie das Projekt jetzt kompilieren:
Abb. 8 Hinzufügen eines Fensters zur schnellen Bearbeitung von Handelssignalen.
Jetzt enthält das Kästchen alle Schaltflächen zur Signalbearbeitung, aber die Idee ist, nur die erstellten Signale anzuzeigen. Fügen wir also eine Prüfung für die aktuelle Anzahl der verfügbaren Signale hinzu. Dies kann durch das Ereignis Dialogboxöffnung erfolgen:
//--- Opening a dialog window if(id==CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX) { if(m_current_step<4) return; for(int i=0; i<5; i++) { if(!FileIsExist("Signal Monitor\\signal_"+string(i)+".bin")) m_fast_editor[i].Hide(); } }
Hier wird geprüft, ob Dateien mit Handelssignal vorhanden sind. Dabei werden nur die früher erzeugten Signale angezeigt. Nun sollte ein Klick auf die Schaltfläche mit dem erzeugten Signal das Bearbeitungsfenster für dieses Signal öffnen. Dies geschieht im Abschnitt Klick auf die Schaltfläche Ereignis.
//--- Trading signal editing for(int i=0; i<5; i++) { if(lparam==m_fast_editor[i].Id()) { m_fast_edit.CloseDialogBox(); LoadSignalSet(i); m_new_signal.LabelText("Save"); m_new_signal.Update(true); RebuildParameters(m_indicator_type.GetListViewPointer().SelectedItemIndex()); m_set_window.OpenWindow(); m_number_signal=i; } }
Ein Klick auf eines der Signale im Schnellbearbeitungsfenster öffnet das Einstellungsfenster und lädt die zuvor gespeicherten Daten dieses Handelssignals. Sobald die erforderlichen Daten geändert wurden, sollten die neuen Einstellungen in eine Datei geschrieben werden. In diesem Fall brauchen wir nicht das gesamte Verfahren zur Einrichtung des Monitors abzuschließen.
Lokalisierung der Anwendung
Um die Lokalisierungsaufgabe zu lösen, ist es notwendig, alle zu übersetzenden GUI-Elemente zu bestimmen, wobei einige von ihnen so belassen werden sollten, wie sie sind, da ihre Namen allgemein anerkannt sind. Wir werden einen einfachen Mechanismus verwenden: Wir werden ein String-Array mit Daten erstellen, die je nach gewählter Sprache für die Ersetzung in Oberflächenelementen verwendet werden. Wir werden zwei Sprachen haben: Russisch und Englisch. Zuerst erstellen wir in der Datei SignalMonitor.mq5 eine Enumeration, die es uns ermöglicht, die gewünschte UI-Sprache beim Start auszuwählen. Die Namen für einige der Elemente werden nach englischen Standards festgelegt.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum UPDATE { MINUTE, // 1 minute MINUTE_15, // 15 minutes MINUTE_30, // 30 minutes HOUR, // 1 hour HOUR_4 // 4 hour }; enum LANG { RUSSIAN, // Russian ENGLISH // English }; //+------------------------------------------------------------------+ //| Expert Advisor input parameters | //+------------------------------------------------------------------+ input int Inp_BaseFont = 10; // Base Font input color Caption = C'0,130,225'; // Caption Color input color Background = clrWhiteSmoke; // Back color input LANG Language = ENGLISH; // Interface language input UPDATE Update = MINUTE; // Update interval
Um Informationen über die ausgewählte Sprache an die Schnittstelle zu übergeben, erstellen Sie eine Variable im öffentlichen Bereich der Basisklasse CProgram.
//--- int m_language;
Der Index der ausgewählten Sprache wird der Variable während der Anwendungsinitialisierung zugewiesen.
program.m_language=Language;
Erstellen wir dann ein Array im 'private' Teil der Basisklasse, das als Empfänger von Daten dient, die in der Schnittstelle entsprechend der gewählten Sprache ersetzt werden sollen. Erstellen wir auch eine Methode, die Daten in die Schnittstelle lädt.
string m_lang[]; void ChangeLanguage(void);
Implementieren wir nun die deklarierte Methode in der Datei Program.mqh und setzen die Sprachwerte auf die entsprechenden Felder der einzelnen GUI-Elemente.
//+------------------------------------------------------------------+ //| Changing the interface language | //+------------------------------------------------------------------+ void CProgram::ChangeLanguage(void) { //--- #define ITEMS 40 ArrayResize(m_lang,ITEMS); string rus[ITEMS]= { "Монитор Сигналов Шаг 1: Выбор Символов","Все","Мажоры","Кроссы", "Назад","Далее","Загрузка(L)","Сохранить(S)","Имя шаблона","Монитор сигналов Шаг 2: Выбор таймфреймов", "Все","Младшие","Старшие", "Монитор Сигналов Шаг 3: Создание торговых сигналов","Создать","Добавить сигнал","Список сигналов", "Редактор торговых сигналов","Тип индикатора","1.Настройки индикатора","Примен. цена", "Введите путь индикатора","Введите параметры индикатора через запятую", "2.Настройка сигнала","Правило","Метка","Значение","Текст","Цвет метки","Фон","Кант","Подсказка", "Изображение","Таймфреймы","Добавить","Отмена","Монитор торговых сигналов","Номер буфера","Сохранить" }; string eng[ITEMS]= { "Signal Monitor Step 1: Choose Symbols","ALL","Major","Crosses", "Back","Next","Load(L)","Save(S)","Template name","Signal Monitor Step 2: Choose Timeframes", "ALL","Junior","Senior", "Signal Monitor Step 3: Creating Trading Signals","Create","Add Signal","Signal List", "Signal Monitor Edit Signal","Indicator Type","1.Indicator Settings","Applied Price", "Enter the indicator path","Enter indicator parameters separated by commas", "2.Signal Settings","Rule","Label","Value","Text","Label Color","Use Background","Use Border","Use Tooltip", "Use Image","Timeframes","Add","Cancel","Signal Monitor","Buffer number","Save" }; //--- Russian if(m_language==0) ArrayCopy(m_lang,rus); //--- English else ArrayCopy(m_lang,eng); }
So haben wir zusätzlich die russische Sprache implementiert (Abb.9). Sie können auf ähnliche Weise Ihre bevorzugte Sprache hinzufügen
Abb. 9 Ergebnis der GUI-Lokalisierung.
Zusätzliche Eigenschaften
Einige zusätzliche Funktionen werden den visuellen Teil des Monitors verbessern und es wird die Möglichkeit bieten, schnell zu dem Symboldiagramm zu wechseln, in dem ein Signal aufgetaucht ist. Der visuelle Teil ist die Erweiterung des Signalblocks, da die derzeit verwendete Form klein erscheint. Finden Sie die Methode CreateSignalButton() und vergrößern Sie die Größe der Signalblöcke, und passen Sie die Position der Elemente innerhalb dieser Blöcke und die Anordnung der Blöcke relativ zueinander in der Methode To_Monitor() an.
button.XSize(60);
button.YSize(30);
button.IconXGap(2);
button.IconYGap(11);
button.LabelXGap(19);
button.LabelYGap(10);
//--- Symbols int sy=ArraySize(m_symbols); ArrayResize(m_symbol_label,sy); for(int i=0; i<sy; i++) { if(!CreateSymbolLabel(m_symbol_label[i],5,m_step_window.CaptionHeight()+40+i*35,m_symbols[i])) return; m_symbol_label[i].Update(true); } //--- Timeframes int tf=ArraySize(m_timeframes); ArrayResize(m_timeframe_label,tf); //--- for(int i=0; i<tf; i++) { if(!CreateTimeframeLabel(m_timeframe_label[i],110+65*i,m_step_window.CaptionHeight()+3,m_timeframes[i])) return; m_timeframe_label[i].Update(true); } //-- Signal blocks int k=0; ArrayResize(m_signal_button,sy*tf); for(int j=0; j<sy; j++) { for(int i=0; i<tf; i++) { if(!CreateSignalButton(m_signal_button[k],m_timeframe_label[i].XGap()+m_timeframe_label[i].XSize()/2,m_step_window.CaptionHeight()+35+j*35)) return; m_signal_button[k].Update(true); k++; } } //--- m_current_step=4; //--- Resize window AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+15,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+10);
Diese Monitor-Implementierung ist für die Nachverfolgung viel bequemer.
Abb. 10 Größenänderung von Signalblöcken und Anpassung der Monitorschnittstelle.
Lassen Sie uns nun das Öffnen eines Diagramms für das Symbol und den Zeitrahmen, in dem das Signal gefunden wurde, implementieren. Das Diagramm sollte durch einen Klick auf den entsprechenden Block geöffnet werden. Fügen wir in der Methode OnEvent(), im Abschnitt Klick auf die Schaltfläche Ereignis folgendes hinzu (weil Signalblöcke Schaltflächen sind):
//--- CLICKING ON THE SIGNAL BLOCK for(int i=0; i<ArraySize(m_signal_button); i++) { if(lparam==m_signal_button[i].Id()) ChartOpen(GetSymbol(i),GetTimeframe(i)); }
Ist alles ganz einfach. Zu diesem Zeitpunkt ist die derzeitige Entwicklungsphase abgeschlossen. Im nächsten Teil werden wir das Signalsuchsystem weiter verbessern, das Konzept eines zusammengesetzten Signals einführen und die Möglichkeiten der Monitorsteuerung erweitern.
Schlussfolgerung
Das unten angehängte Archiv enthält alle beschriebenen Dateien in Ordnern. Für einen korrekten Betrieb sollten Sie den Ordner MQL5 im Stammverzeichnis des Terminals speichern. Um das Stammverzeichnis des Terminals zu öffnen, in dem sich der Ordner MQL5 befindet, drücken Sie die Tastenkombination Strg+Umschalt+D im Terminal MetaTrader 5 oder verwenden Sie das Kontextmenü wie in Abb. 11 unten dargestellt.
Abb. 11. Öffnen des Ordners MQL5 im Stammordner von MetaTrader 5
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/7678
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.