
Das MQL5-Kochbuch: Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden
Einleitung
In diesem Beitrag betrachten wir die Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden in einem bestimmten Zeitraum. Die wichtigsten Themen wurden bereits im vorhergehenden Beitrag zur Programmierung mehrwährungsfähiger Indikatoren besprochen, Das MQL5-Kochbuch: Entwicklung eines Indikators für die Volatilität mehrerer Symbole in MQL5. Diesmal gehen wir also nur auf jene neuen Features und Funktionen ein, an denen wesentliche Änderungen vorgenommen wurden. Wenn Sie ein Neuling in der Programmierung von mehrwährungsfähigen Indikatoren sind, empfehle ich Ihnen, zuerst den vorherigen Beitrag zu lesen.
Wir werden in diesem Beitrag auf die folgenden Themen eingehen:
- Ändern der Eigenschaften des Diagramms.
- Verarbeitung der Ereignisse CHARTEVENT_OBJECT_DRAG (Ziehen eines Diagrammobjekts) und CHARTEVENT_CHART_CHANGE (Ändern der Größe des Diagramms oder der Eigenschaften des Diagramms mithilfe des Dialogfensters mit den Eigenschaften).
- Darstellung von Indikatorpuffern mit mehr als einer Farbe.
- Definieren von Maxima und Minima in Indikatorpuffern innerhalb des sichtbaren Bereichs zum Festlegen eines Maximums/Minimums des Diagramms.
- Umkehrung einer Serie.
Die resultierende Codemenge für unseren Indikator ist mit etwa 1500 Zeilen ziemlich groß. Deshalb verteilen wir alle Funktionen in separaten Dateien und verknüpfen sie mit der Hauptdatei des Projekts. Es wird drei Kategorien von Funktionen für externe Dateien geben:
- Checks.mqh – Funktionen zum Durchführen diverser Prüfungen und zum Herunterladen verfügbarer Daten.
- Objects.mqh – Funktionen zum Verwalten von grafischen Objekten.
- Chart.mqh – Funktionen zum Verwalten von Diagrammeigenschaften.
Alle Funktionen, die nicht zu den oben aufgezählten Kategorien gehören, verbleiben in der Hauptdatei.
Entwicklung des Indikators
Fahren wir mit der Entwicklung des Indikators fort. Als Erstes müssen wir ein neues Projekt erstellen. Erstellen Sie hierzu im Verzeichnis MetaTrader 5\MQL5\Indicators einen Ordner mit dem Namen unseres Indikators und darin den Ordner Include, in dem die Include-Dateien abgelegt werden. Als Nächstes erstellen sie die Hauptdatei im Ordner des Indikators. Dies kann manuell durch das Erstellen einer Textdatei mit der Erweiterung *.mq5 oder mithilfe einer Vorlage im MQL5 Wizard bewerkstelligt werden. Zusätzlich zu den Kernfunktionen des Programms, OnInit(), OnDeinit() und OnCalculate(), verwenden wir auch OnChartEvent() und OnTimer().
Wie im vorherigen Beitrag lassen wir neben dem aktuellen Symbol Daten für 5 in den externen Parametern festgelegte Symbole anzeigen. Doch diesmal geben wir anstellte von Werten, die durch eine Formel errechnet wurden, rohe Preisdaten im Diagramm wieder. Dem Benutzer steht es frei, die Art der Darstellung der Daten in den externen Parametern aus der Dropdown-Liste auszuwählen: Linie, Balken oder Kerzen.
Wenn wir die Daten nur als einfarbige Linien darstellen müssten, würde es ausreichen, die Menge der Puffer gleich der Menge der Symbole in den Eigenschaften des Indikators (#property) anzugeben. Doch da es zwei Modi für das Zeichnen von Serien als Balken und Kerzen gibt, brauchen wir mehr Puffer für den zweifarbigen Modus: vier Puffer zum Darstellen jeder Serie und einen Puffer zum Festlegen der Farbe (abhängig von der Bedingung) für jedes Element in einer Grafikserie.
Für jede Serie müssen die Farben in den Programmeigenschaften festgelegt werden. Listen Sie sie dafür einfach durch Kommata getrennt auf. Als Erstes kommt die im einfarbigen Modus verwendete Farbe. Im zweifarbigen Modus wird sie für aufwärts gerichtete Balken/Kerzen genutzt. Die zweite Farbe wird nur im zweifarbigen Modus für abwärts gerichtete Balken/Kerzen verwendet.
Die Codes all dieser Parameter sind nachfolgend aufgeführt:
#property indicator_chart_window // Indicator is in the main window #property indicator_buffers 25 // Number of buffers for indicator calculation #property indicator_plots 5 // Number of plotting series //--- Indicator buffers colors #property indicator_color1 clrDodgerBlue,C'0,50,100' #property indicator_color2 clrLimeGreen,C'20,80,20' #property indicator_color3 clrGold,C'160,140,0' #property indicator_color4 clrAqua,C'0,140,140' #property indicator_color5 clrMagenta,C'130,0,130'
Deklarieren wir mit der Direktive #define Konstanten und nehmen mit der Befehlszeile #include Dateien mit Funktionen auf, die bereits oben beschrieben wurden, sowie die Klasse aus der Standardbibliothek zum Arbeiten mit der Arbeitsfläche:
//--- Constants #define RESET 0 // Returning the indicator recalculation command to the terminal #define SYMBOLS_COUNT 5 // Number of symbols //--- Include the class for working with the canvas #include <Canvas\Canvas.mqh> //--- Include the class for working with the canvas #include "Include/Checks.mqh" #include "Include/Chart.mqh" #include "Include/Objects.mqh"
Fügen Sie die Aufzählungen ENUM_DRAWTYPE und ENUM_START_POINT hinzu, um Dropdown-Listen zu erzeugen, die die Auswahl des Darstellungstypen der Preisdaten und den Modus des Ausgangspunkts des Preisunterschieds in den externen Parametern ermöglichen:
//--- Drawing type of the price data enum ENUM_DRAWTYPE { LINE =0, // Line BARS =1, // Bars CANDLES=2 // Candlesticks }; //--- Mode of the price divergence starting point enum ENUM_START_POINT { VERTICAL_LINE=0, // Vertical line MONTH =1, // Month WEEK =2, // Week DAY =3, // Day HOUR =4 // Hour };
Die Arten der Darstellung der Daten wurden oben bereits beschrieben, sehen wir uns also näher an, was der Ausgangspunkt des Preisunterschieds bedeutet.
Insgesamt wird es fünf Modi geben: Vertikale Linie, Monat, Woche, Tag und Stunde. Für den Modus Vertikale Linie wird beim Laden des Indikators im Diagramm eine vertikale Linie hinzugefügt. Durch Ziehen dieser Linie bestimmen Sie den Balken, bei dem sich die Preise aller Symbole an einem Punkt treffen. Der Öffnungspreis des festgelegten Balkens für das aktuelle Symbol gilt als Bezugspunkt dieses Treffens. In allen anderen Modi erhält das Programm die Information, dass die Preise sich jedes Mal am Anfang des festgelegten Zeitraums treffen müssen, d. h. am Anfang jedes Monats, am Anfang jeder Woche, am Anfang jedes Tages oder am Anfang jeder Stunde.
Nachfolgend sehen Sie die Liste der Eingabeparameter des Indikators:
//--- External parameters input ENUM_DRAWTYPE DrawType =CANDLES; // Drawing type input ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Start of price divergence input bool TwoColor =false; // Two-color bars/candlesticks sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - input string Symbol02 ="GBPUSD"; // Symbol 2 input bool Inverse02 =false; // Inverse symbol 2 input string Symbol03 ="AUDUSD"; // Symbol 3 input bool Inverse03 =false; // Inverse symbol 3 input string Symbol04 ="NZDUSD"; // Symbol 4 input bool Inverse04 =false; // Inverse symbol 4 input string Symbol05 ="USDCAD"; // Symbol 5 input bool Inverse05 =false; // Inverse symbol 5 input string Symbol06 ="USDCHF"; // Symbol 6 input bool Inverse06 =false; // Inverse symbol 6
Symbole werden ab 2 durchnummeriert, da 1 das aktuelle Symbol im Diagramm ist.
Für jedes eingeschlossene Symbol kann die Umkehrung angewendet werden. Umkehrung bedeutet, dass die Daten des Symbols umgedreht dargestellt werden. Das kann nützlich sein, wenn die Liste der analysierten Symbole Währungspaare enthält, in denen eine Währung (beispielsweise US-Dollar) sowohl die Basiswährung als auch die Gegenwährung sein kann. Beispielsweise ist im Währungspaar EURUSD der US-Dollar die Gegenwährung, im Währungspaar USDCHF ist er die Basiswährung. Falls das aktuelle Symbol im Diagramm EURUSD ist, können Sie die Umkehrung für USDCHF aktivieren, wodurch die Darstellung der Preise bequemer zu analysieren wird.
Nachfolgend sehen Sie die Liste der globalen Variablen und Arrays:
//--- Structure of the indicator buffers arrays struct buffers { double open[]; // Open prices buffer double high[]; // High prices buffer double low[]; // Low prices buffer double close[]; // Close prices buffer double icolor[]; // Buffer to determine the color of element }; buffers buffer_data[SYMBOLS_COUNT]; //--- Load the class CCanvas canvas; //--- Variables/arrays for copying data from OnCalculate() int OC_rates_total =0; // Size of input time series int OC_prev_calculated =0; // Bars processed at the previous call datetime OC_time[]; // Opening time double OC_open[]; // Open prices double OC_high[]; // High prices double OC_low[]; // Low prices double OC_close[]; // Close prices long OC_tick_volume[]; // Tick volumes long OC_volume[]; // Real volumes int OC_spread[]; // Spread //--- For the purpose of storing and checking the time of the first bar in the terminal datetime series_first_date[SYMBOLS_COUNT]; datetime series_first_date_last[SYMBOLS_COUNT]; //--- Time array of the bar from which we will start drawing datetime limit_time[SYMBOLS_COUNT]; //--- Symbol names array string symbol_names[SYMBOLS_COUNT]; //--- Array of symbol inverse flags bool inverse[SYMBOLS_COUNT]; //--- Colors of indicator lines color line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta}; //--- String representing the lack of the symbol string empty_symbol="EMPTY"; //--- Chart properties int window_number =WRONG_VALUE; // Indicator window number int chart_width =0; // Chart width int chart_height =0; // Chart height int last_chart_width =0; // Last saved chart width int last_chart_height =0; // Last saved chart height int chart_center_x =0; // Horizontal center of chart int chart_center_y =0; // Vertical center of chart color color_bar_up =clrRed; // Up bar color color color_bar_down =C'100,0,0'; // Down bar color string indicator_shortname ="MS_PriceDivergence"; // Short name of the indicator string prefix =indicator_shortname+"_"; // Prefix for objects //--- Name of vertical line of the price divergence starting point string start_price_divergence=prefix+"start_price_divergence"; //--- Canvas properties string canvas_name =prefix+"canvas"; // Canvas name color canvas_background =clrBlack; // Canvas background color uchar canvas_opacity =190; // Opacity int font_size =16; // Font size string font_name ="Calibri"; // Font ENUM_COLOR_FORMAT clr_format =COLOR_FORMAT_ARGB_RAW; // Color components should be correctly set by the user //--- Canvas messages string msg_prepare_data ="Preparing data! Please wait..."; string msg_not_synchronized ="Unsynchronized data! Please wait..."; string msg_load_data =""; string msg_sync_update =""; string msg_last =""; //--- ENUM_TIMEFRAMES timeframe_start_point =Period(); // Timeframe for the price divergence starting point datetime first_period_time =NULL; // Time of the first specified period on chart double divergence_price =0.0; // Price of the price divergence starting point datetime divergence_time =NULL; // Time of the price divergence starting point double symbol_difference[SYMBOLS_COUNT]; // Difference in price relative to the current symbol double inverse_difference[SYMBOLS_COUNT]; // Difference that is formed when calculating inversion
Als nächstes betrachten wir Funktionen, die während der Initialisierung des Indikators verwendet werden. Im Allgemeinen gibt es keine wesentlichen Änderungen im Vergleich zur OnInit()-Funktion aus dem vorherigen Beitrag.
Fügen wir eine Prüfung hinzu, die prüft, wo der Indikator genutzt wird. Das Problem ist, dass die Entwickler des Terminals noch nicht alle Funktionen für die Steuerung der Eigenschaften der Diagramme im Strategietester implementiert haben. Deshalb beschränken wir unseren Indikator auf die Nutzung außerhalb des Strategietesters. Dazu schreiben wir eine einfache Funktion, CheckTesterMode(). Sie wird sich in der Datei Checks.mqh befinden:
//+------------------------------------------------------------------+ //| Checks if indicator is used in Strategy Tester | //+------------------------------------------------------------------+ bool CheckTesterMode() { //--- Report that indicator is not intended to be used in Strategy Tester if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE) || MQLInfoInteger(MQL_OPTIMIZATION)) { Comment("Currently, the <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> indicator is not intended to be used in Strategy Tester!"); return(false); } //--- return(true); }
Eine weitere neue Funktion, SetBarsColors(), ist für die Festlegung der Farben von Balken/Kerzen des aktuellen Symbols vorgesehen. Sie wird sich in der Datei Chart.mqh befinden.
//+------------------------------------------------------------------+ //| Sets colors for the current symbol bars | //+------------------------------------------------------------------+ void SetBarsColors() { //--- Color for the up bar, shadows and body borders of bull candlesticks ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up); //--- Body color of a bull candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up); //--- Line chart color and color of "Doji" Japanese candlesticks ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up); //--- For two-color mode if(TwoColor) { //--- Color for the down bar, shadows and body borders of bear candlesticks ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down); //--- Body color of a bear candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down); } //--- If two-color mode is turned off else { //--- Color for the down bar, shadows and body borders of bear candlesticks ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up); //--- Body color of a bear candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up); } }
Während der Initialisierung müssen wir festlegen, welcher Modus im externen Parameter StartPriceDivergence ausgewählt wird. Falls Vertikale Linie ausgewählt wird, wird die globale Variable timeframe_start_point mit ihrem Standardwert, d. h. dem aktuellen Timeframe, zugewiesen. Andernfalls wird der ausgewählte Timeframe angewendet. Schreiben wir zu diesem Zweck die Funktion InitStartPointTF():
//+------------------------------------------------------------------+ //| Identifies timeframe for the price starting point mode | //+------------------------------------------------------------------+ void InitStartPointTF() { //--- Exit if vertical line mode is selected if(StartPriceDivergence==VERTICAL_LINE) return; //--- Otherwise define the timeframe switch(StartPriceDivergence) { case MONTH : timeframe_start_point=PERIOD_MN1; break; case WEEK : timeframe_start_point=PERIOD_W1; break; case DAY : timeframe_start_point=PERIOD_D1; break; case HOUR : timeframe_start_point=PERIOD_H1; break; } }
Die Funktion CheckInputParameters() sieht im Gegensatz zu der aus dem vorherigen Beitrag nun so aus:
//+------------------------------------------------------------------+ //| Checks input parameters for correctness | //+------------------------------------------------------------------+ bool CheckInputParameters() { //--- For all other modes except the 'Vertical Line' if(StartPriceDivergence!=VERTICAL_LINE) { //--- If the current period is greater than or equal to the specified period of the price divergence starting point, report of it and exit if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point)) { Print("Current timeframe should be less than one specified in the Start Price Divergence parameter!"); Comment("Current timeframe should be less than one specified in the Start Price Divergence parameter!"); return(false); } } //--- return(true); }
Arrays werden genauso initialisiert wie im vorherigen Beitrag. Nur die Namen und die Menge der Arrays haben sich geändert.
//+------------------------------------------------------------------+ //| First initialization of arrays | //+------------------------------------------------------------------+ void InitArrays() { ArrayInitialize(limit_time,NULL); ArrayInitialize(symbol_difference,0.0); ArrayInitialize(inverse_difference,0.0); ArrayInitialize(series_first_date,NULL); ArrayInitialize(series_first_date_last,NULL); //--- for(int s=0; s<SYMBOLS_COUNT; s++) { ArrayInitialize(buffer_data[s].open,EMPTY_VALUE); ArrayInitialize(buffer_data[s].high,EMPTY_VALUE); ArrayInitialize(buffer_data[s].low,EMPTY_VALUE); ArrayInitialize(buffer_data[s].close,EMPTY_VALUE); ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE); } } //+------------------------------------------------------------------+ //| Initializes array of symbols | //+------------------------------------------------------------------+ void InitSymbolNames() { symbol_names[0]=AddSymbolToMarketWatch(Symbol02); symbol_names[1]=AddSymbolToMarketWatch(Symbol03); symbol_names[2]=AddSymbolToMarketWatch(Symbol04); symbol_names[3]=AddSymbolToMarketWatch(Symbol05); symbol_names[4]=AddSymbolToMarketWatch(Symbol06); } //+------------------------------------------------------------------+ //| Initializes array of inversions | //+------------------------------------------------------------------+ void InitInverse() { inverse[0]=Inverse02; inverse[1]=Inverse03; inverse[2]=Inverse04; inverse[3]=Inverse05; inverse[4]=Inverse06; }
An der Funktion SetIndicatorProperties() wurden wesentliche Änderungen vorgenommen. Faktisch ist es eine ganz neue Funktion. Jetzt werden die entsprechenden Eigenschaften je nachdem, welcher Modus der Datendarstellung ausgewählt ist, während der Initialisierung festgelegt.
//+------------------------------------------------------------------+ //| Sets indicator properties | //+------------------------------------------------------------------+ void SetIndicatorProperties() { //--- Set the short name IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname); //--- Set the number of decimal digits IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- In the 'Line' mode we need only one buffers that displays the open price if(DrawType==LINE) { for(int s=0; s<SYMBOLS_COUNT; s++) SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA); } //--- In other modes we use all prices for drawing // bars/candlesticks and additional buffer for the two-color mode else if(DrawType==BARS || DrawType==CANDLES) { for(int s=0; s<SYMBOLS_COUNT; s++) { static int buffer_number=0; SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX); buffer_number++; } } //--- Set labels for the current timeframe // In the 'Line' mode only opening price is used if(DrawType==LINE) { for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close"); } //--- In other modes all prices of bars/candlesticks // ";" is used as a separator else if(DrawType==BARS || DrawType==CANDLES) { for(int s=0; s<SYMBOLS_COUNT; s++) { PlotIndexSetString(s,PLOT_LABEL, symbol_names[s]+",Open;"+ symbol_names[s]+",High;"+ symbol_names[s]+",Low;"+ symbol_names[s]+",Close"); } } //--- Set the type of lines for indicator buffers //--- Line if(DrawType==LINE) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE); //--- Bars if(DrawType==BARS) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS); //--- Candlesticks if(DrawType==CANDLES) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES); //--- Set the type of lines for data of current symbol //--- Line if(DrawType==LINE) ChartSetInteger(0,CHART_MODE,CHART_LINE); //--- Bars if(DrawType==BARS) ChartSetInteger(0,CHART_MODE,CHART_BARS); //--- Candlesticks if(DrawType==CANDLES) ChartSetInteger(0,CHART_MODE,CHART_CANDLES); //--- Set the line width for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1); //--- Set the line color for the 'Line' mode if(DrawType==LINE) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]); //--- Display data in Data Window only for existing symbols for(int s=0; s<SYMBOLS_COUNT; s++) { if(symbol_names[s]!=empty_symbol) PlotIndexSetInteger(s,PLOT_SHOW_DATA,true); else PlotIndexSetInteger(s,PLOT_SHOW_DATA,false); } //--- Empty value for plotting where nothing will be drawn for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE); }
Und zu guter Letzt wird eine weitere neue Funktion, SetDivergenceLine(), in OnInit() verwendet. Sie setzt die vertikale grüne Linie zur Steuerung des Ausgangspunkts des Preisunterschieds im Modus Vertikale Linie.
//+------------------------------------------------------------------+ //| Sets vertical line for price divergence starting point | //+------------------------------------------------------------------+ void SetDivergenceLine() { //--- If there is no vertical line yet, set it if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0) //--- Place a vertical line on the true bar CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence, 2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n"); //--- For all other modes except the 'Vertical Line' if(StartPriceDivergence!=VERTICAL_LINE) DeleteObjectByName(start_price_divergence); }
Nachfolgend sehen Sie die Darstellung aller oben beschriebenen Erwägungen innerhalb der Funktion OnInit(). Wenn alles in separate Funktionen und Dateien aufgeteilt ist, wird es sehr einfach, den Code des Programms zu lesen.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Check if indicator is currently being used in Strategy Tester if(!CheckTesterMode()) return(INIT_FAILED); //--- Set the color for bars/candlesticks SetBarsColors(); //--- Define the timeframe for the price divergence starting point InitStartPointTF(); //--- Check input parameters for correctness if(!CheckInputParameters()) return(INIT_PARAMETERS_INCORRECT); //--- Set the timer at 1-second intervals EventSetMillisecondTimer(1000); //--- Set the font to be displayed on the canvas canvas.FontSet(font_name,font_size,FW_NORMAL); //--- Initialization of arrays InitArrays(); //--- Initialize the array of symbols InitSymbolNames(); //--- Initialize the array of inversions InitInverse(); //--- Set indicator properties SetIndicatorProperties(); //--- Set vertical line of the price divergence start SetDivergenceLine(); //--- Clear the comment Comment(""); //--- Refresh the chart ChartRedraw(); //--- Initialization completed successfully return(INIT_SUCCEEDED); }
In der Funktion OnCalculate() ist der Code des Programms so gut wie unverändert geblieben. Im vorherigen Beitrag füllte das Programm nach der Überprüfung der Verfügbarkeit der Daten zuerst Hilfs-Arrays aus und befüllte erst dann die Indikatorpuffer mit den vorbereiteten Daten. Diesmal versuchen wir, alles in einer einzigen Schleife umzusetzen.
Ich habe die Funktionen zum Validieren und Laden von Daten strenger gestaltet. Nun durchläuft jeder Wert, den Sie erhalten möchten, eine festgelegte Anzahl von Versuchen. Wenn ein Wert erhalten wird, wird die Schleife angehalten. Und da wir nun Modi haben, in denen wir den Ausgangspunkt eines Zeitraums (Monat, Woche, Tag, Stunde) festlegen müssen, erhalten wir die Startzeit des Zeitraums über den höheren Timeframe. Deshalb habe ich eine zusätzliche Funktion erstellt, die LoadAndFormData() ähnlich ist und mit LoadAndFormDataHighTF() ähnlich benannt ist. Ihr Code ist dem des Originals sehr ähnlich, weshalb ich ihn hier nicht aufführen werde.
Die Überprüfung der Verfügbarkeit von Daten für den aktuellen und höhere Timeframes wurde in der einzelnen Funktion CheckAvailableData() umgesetzt:
//+------------------------------------------------------------------+ //| Checks the amount of available data for all symbols | //+------------------------------------------------------------------+ bool CheckAvailableData() { int attempts=100; //--- for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If this symbol is available if(symbol_names[s]!=empty_symbol) { datetime time[]; // Array for checking the number of bars int total_period_bars =0; // Number of bars of the current period datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal //--- Get the first date of the current time frame data in the terminal terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE); //--- Get the number of available bars from the date specified total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent()); //--- Check the readiness of bar data for(int i=0; i<attempts; i++) { //--- Copy the specified amount of data if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time)) { //--- If the required amount has been copied, terminate the loop if(ArraySize(time)>=total_period_bars) break; } } //--- If the amount of data copied is not sufficient, one more attempt is required if(ArraySize(time)==0 || ArraySize(time)<total_period_bars) { msg_last=msg_prepare_data; ShowCanvasMessage(msg_prepare_data); OC_prev_calculated=0; return(false); } } } //--- Exit if current mode is vertical line of the price divergence starting point if(StartPriceDivergence==VERTICAL_LINE) return(true); else { datetime time[]; // Array for checking the number of bars int total_period_bars =0; // Number of bars of the current period datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal //--- Get the first date of the current time frame data in the terminal for(int i=0; i<attempts; i++) if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0) break; //--- Get the number of available bars from the date specified for(int i=0; i<attempts; i++) if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0) break; //--- Check the readiness of bar data for(int i=0; i<attempts; i++) //--- Copy the specified amount of data if(CopyTime(Symbol(),timeframe_start_point, terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0) break; //--- If the amount of data copied is not sufficient, one more attempt is required if(ArraySize(time)<=0 || total_period_bars<=0) { msg_last=msg_prepare_data; ShowCanvasMessage(msg_prepare_data); OC_prev_calculated=0; return(false); } } //--- return(true); }
Die Funktion FillIndicatorBuffers() wurde für die vorliegende Aufgabe viel komplexer. Der Grund dafür ist, dass es nun mehrere Modi gibt, von denen jeder seine eigenen Aktionen benötigt. Tatsächlich lässt sich alles in vier Schritte aufteilen.
- Abrufen der Daten für das angegebene Symbol.
- Abrufen der Daten für den höheren Timeframe und Bestimmen der Zeit und des Preisniveaus, bei denen sich die Preise aller Symbole treffen.
- Berechnen von Werten und Ausfüllen des Indikatorpuffers.
- Überprüfung der berechneten Werte.
Der Code der Funktion enthält detaillierte Kommentare für Ihre Lernzwecke:
//+------------------------------------------------------------------+ //| Fills indicator buffers | //+------------------------------------------------------------------+ void FillIndicatorBuffers(int i,int s,datetime const &time[]) { MqlRates rates[]; // Data structure double period_open[]; // Opening price for bar at the price divergence starting point datetime period_time[]; // Time of the price divergence starting point int attempts=100; // Number of copying attempts datetime high_tf_time=NULL; // Time of higher timeframe's bar //--- Exit if we are out of "true" bars zone if(time[i]<limit_time[s]) return; //--- Reset the last error ResetLastError(); //--- Get data of current bar for the specified symbol for(int j=0; j<attempts; j++) if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1) { ResetLastError(); break; } //--- Exit if failed to get data if(ArraySize(rates)<1 || GetLastError()!=0) return; //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time of the current symbol or // empty values are fetched if(rates[0].time==NULL || time[i]!=rates[0].time || time[i]<first_period_time || rates[0].low==EMPTY_VALUE || rates[0].open==EMPTY_VALUE || rates[0].high==EMPTY_VALUE || rates[0].close==EMPTY_VALUE) { //--- Write empty value if(DrawType!=LINE) { buffer_data[s].low[i] =EMPTY_VALUE; buffer_data[s].open[i] =EMPTY_VALUE; buffer_data[s].high[i] =EMPTY_VALUE; } buffer_data[s].close[i]=EMPTY_VALUE; return; } //--- If current mode is vertical line of the price divergence starting point if(StartPriceDivergence==VERTICAL_LINE) { //--- Get the time of the line divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME); //--- Get the time of the first bar first_period_time=time[0]; } //--- For all other modes, we will keep track the beginning of period else { //--- If we are here for the first time, store data of the first bar of higher timeframe if(divergence_time==NULL) { ResetLastError(); //--- Get opening time of the first bar of higher timeframe for(int j=0; j<attempts; j++) if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1) { ResetLastError(); break; } //--- Exit if failed to get price/time if(ArraySize(period_time)<1 || GetLastError()!=0) return; //--- Otherwise store time of the first bar of higher timeframe else first_period_time=period_time[0]; } //--- If current bar's time on the current timeframe is before the first bar's time on higher timeframe if(time[i]<first_period_time) high_tf_time=first_period_time; //--- Otherwise we will receive data of the last bar of the higher timeframe with respect to the current bar on the current timeframe else high_tf_time=time[i]; //--- Reset the last error ResetLastError(); //--- Get the opening price of the first bar of the higher timeframe for(int j=0; j<attempts; j++) if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1) { ResetLastError(); break; } //--- Get opening time of the first bar of higher timeframe for(int j=0; j<attempts; j++) if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1) { ResetLastError(); break; } //--- Exit if failed to get price/time if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0) return; //--- If the current timeframe's time is before the first period's time or // time of specified period is not equal to the one in memory if(time[i]<first_period_time || divergence_time!=period_time[0]) { symbol_difference[s] =0.0; // Zero out difference in symbol prices inverse_difference[s] =0.0; // Zero our difference of inversion //--- Store time of the price divergence starting point divergence_time=period_time[0]; //--- Store price of the price divergence starting point divergence_price=period_open[0]; //--- Set vertical line in the beginning of the price divergence start CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time), 2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n"); } } //--- If current mode is 'Vertical Line' and bar's time is less than line's time if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time) { //--- Keep zero values of difference symbol_difference[s] =0.0; inverse_difference[s] =0.0; //--- For the 'Line' drawing mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=rates[0].close-symbol_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =rates[0].low-symbol_difference[s]; buffer_data[s].open[i] =rates[0].open-symbol_difference[s]; buffer_data[s].high[i] =rates[0].high-symbol_difference[s]; buffer_data[s].close[i] =rates[0].close-symbol_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } //--- For all other modes else { //--- If inversion of symbol data is required if(inverse[s]) { //--- If new period has started, recalculate variables if(symbol_difference[s]==0.0) { //--- For the 'Vertical Line' mode if(StartPriceDivergence==VERTICAL_LINE) { //--- Calculate the difference symbol_difference[s] =rates[0].open-OC_open[i]; inverse_difference[s] =OC_open[i]-(-OC_open[i]); } //--- For all other modes else { //--- Calculate the difference symbol_difference[s] =rates[0].open-divergence_price; inverse_difference[s] =divergence_price-(-divergence_price); } } //--- In the 'Line' mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =-(rates[0].low-symbol_difference[s])+inverse_difference[s]; buffer_data[s].open[i] =-(rates[0].open-symbol_difference[s])+inverse_difference[s]; buffer_data[s].high[i] =-(rates[0].high-symbol_difference[s])+inverse_difference[s]; buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } //--- If inversion is not used, then we need to calculate only the difference between symbol prices at the beginning of period else { //--- If new period has started if(symbol_difference[s]==0.0) { //--- For the 'Vertical Line' mode if(StartPriceDivergence==VERTICAL_LINE) symbol_difference[s]=rates[0].open-OC_open[i]; //--- For all other modes else symbol_difference[s]=rates[0].open-divergence_price; } //--- For the 'Line' drawing mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=rates[0].close-symbol_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =rates[0].low-symbol_difference[s]; buffer_data[s].open[i] =rates[0].open-symbol_difference[s]; buffer_data[s].high[i] =rates[0].high-symbol_difference[s]; buffer_data[s].close[i] =rates[0].close-symbol_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } } //--- Verification of the calculated values // In the 'Line' mode only opening price is used if(DrawType==LINE) { //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time, write empty value if(time[i]!=rates[0].time || time[i]<first_period_time) buffer_data[s].close[i]=EMPTY_VALUE; } //--- For all other modes all prices are used else { //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time of the current symbol or // empty values are fetched if(rates[0].time==NULL || time[i]!=rates[0].time || time[i]<first_period_time || rates[0].low==EMPTY_VALUE || rates[0].open==EMPTY_VALUE || rates[0].high==EMPTY_VALUE || rates[0].close==EMPTY_VALUE) { //--- Write empty value buffer_data[s].low[i] =EMPTY_VALUE; buffer_data[s].open[i] =EMPTY_VALUE; buffer_data[s].high[i] =EMPTY_VALUE; buffer_data[s].close[i] =EMPTY_VALUE; } } }
Beim Studieren der oben aufgeführten Funktion sollten Sie eine weitere benutzerdefinierte Funktion, SetBufferColorIndex(), bemerken. Diese Funktion legt die Farbe im Farbpuffer des Indikators fest.
//+------------------------------------------------------------------+ //| Sets the color for buffer element by condition | //+------------------------------------------------------------------+ void SetBufferColorIndex(int i,int symbol_number,double close,double open) { //--- For two-color mode, check condition if(TwoColor) { //--- If the closing price is more than the opening price, this is up bar, so we use the first color if(close>open) buffer_data[symbol_number].icolor[i]=0; //--- otherwise it is down bar, so we use the second color else buffer_data[symbol_number].icolor[i]=1; } //--- For one-color mode we use the first color for all bars/candlesticks else buffer_data[symbol_number].icolor[i]=0; }
Sobald die Indikatorpuffer ausgefüllt sind, müssen wir das Maximum und Minimum von allen Werten bestimmen, die aktuell im Diagrammfenster zu sehen sind. MQL5 bietet die Möglichkeit, die Nummer des ersten sichtbaren Balkens und die Menge der sichtbaren Balken abzurufen. In CorrectChartMaxMin(), einer weiteren benutzerdefinierten Funktion, profitieren wir von diesen Features. Der Codefluss in dieser Funktion lässt sich in mehrere Schritte einteilen:
- Bestimmen der Nummer des ersten und letzten sichtbaren Balkens.
- Bestimmen des Maximums und Minimums der sichtbaren Balken für das aktuelle Symbol.
- Bestimmen des Maximums und Minimums unter allen Arrays von Symbolen.
- Festlegen des Maximums und Minimums in den Diagrammeigenschaften.
Nachfolgend sehen sie den Code der Funktion CorrectChartMaxMin() in der Datei Chart.mqh.
//+------------------------------------------------------------------+ //| Corrects chart's high/low with respect to all buffers | //+------------------------------------------------------------------+ void CorrectChartMaxMin() { double low[]; // Array of lows double high[]; // Array of highs int attempts =10; // Number of attempts int array_size =0; // Array size for drawing int visible_bars =0; // Number of visible bars int first_visible_bar =0; // Number of the first visible bar int last_visible_bar =0; // Number of the last visible bar double max_price =0.0; // Highest price double min_price =0.0; // Lowest price double offset_max_min =0.0; // Offset from chart's high/low //--- Reset the last error ResetLastError(); //--- Number of visible bars visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS); //--- Number of the first visible bar first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR); //--- Number of the last visible bar last_visible_bar=first_visible_bar-visible_bars; //--- Exit in case of error if(GetLastError()!=0) return; //--- Fix incorrect value if(last_visible_bar<0) last_visible_bar=0; //--- Get the current symbol high/low in visible area of chart for(int i=0; i<attempts; i++) if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars) break; for(int i=0; i<attempts; i++) if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars) break; //--- Exit if failed to get data if(ArraySize(high)<=0 || ArraySize(low)<=0) return; //--- If succeeded to get data, identify high and low in the current symbol arrays else { min_price=low[ArrayMinimum(low)]; max_price=high[ArrayMaximum(high)]; } //--- Get high and low prices in all price arrays for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If current symbol is not present, go to the next one if(symbol_names[s]==empty_symbol) continue; //--- datetime time[]; // Time array int bars_count=0; // Number of bars for calculation //--- Set zero size for arrays ArrayResize(high,0); ArrayResize(low,0); //--- Get the time of the first bar visible on chart for(int i=0; i<attempts; i++) if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars) break; //--- Exit if the amount of data is less than number of visible bars on chart if(ArraySize(time)<visible_bars) return; //--- If time of the first "true" bar is greater than // time of the first visible bar on the chart, then // get available number of bars of the current symbol in loop if(limit_time[s]>time[0]) { //--- Get the array size array_size=ArraySize(time); //--- Get the number of bars from the first "true" one if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0) return; } //--- Else get number of visible bars on chart else bars_count=visible_bars; //--- Index elements in indicator buffers as timeseries ArraySetAsSeries(low,true); ArraySetAsSeries(high,true); //--- Copy data from the indicator buffer // All modes except 'Line' if(DrawType!=LINE) { ArrayCopy(low,buffer_data[s].low); ArrayCopy(high,buffer_data[s].high); } //--- For the 'Line' mode else { ArrayCopy(low,buffer_data[s].close); ArrayCopy(high,buffer_data[s].close); } //--- Get the array size array_size=ArraySize(high); //--- Fill empty values, // so they are not considered when calculating high/low for(int i=0; i<array_size; i++) { if(high[i]==EMPTY_VALUE) high[i]=max_price; if(low[i]==EMPTY_VALUE) low[i]=min_price; } //--- Get high/low with respect to inversion if(inverse[s]) { //--- If no errors occur, store values if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && ArrayMinimum(low,last_visible_bar,bars_count)>=0) { max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]); min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]); } } else { //--- If no errors occur, store values if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && ArrayMaximum(high,last_visible_bar,bars_count)>=0) { min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]); max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]); } } } //--- Calculate offset (3%) form chart's top and bottom offset_max_min=((max_price-min_price)*3)/100; //--- Turn on the fixed chart scale mode. ChartSetInteger(0,CHART_SCALEFIX,true); //--- Set high/low ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min); ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min); //--- Refresh the chart ChartRedraw(); }
Die oben beschriebene Funktion wird beim Verarbeiten des Ereignisses des Ziehens der vertikalen Linie verwendet (und natürlich beim Berechnen der Indikatorwerte in OnCalculate):
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Event of dragging a graphical object if(id==CHARTEVENT_OBJECT_DRAG) { //--- If current mode is vertical line for the price divergence starting point, then update indicator buffers if(StartPriceDivergence==VERTICAL_LINE) OnCalculate(OC_rates_total, 0, OC_time, OC_open, OC_high, OC_low, OC_close, OC_tick_volume, OC_volume, OC_spread); } //--- Event of resizing the chart or modifying the chart properties using the properties dialog window. if(id==CHARTEVENT_CHART_CHANGE) //--- Correct the maximum and minimum of chart with respect to the indicator buffers' values CorrectChartMaxMin(); }
Alle Funktionen sind bereit. Sie können den vollständig kommentierten Code im Anhang dieses Beitrags im Detail betrachten.
Lassen Sie uns vorführen, was wir erhalten haben. Standardmäßig werden die Symbole GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF in den externen Parametern festgelegt. Im nachfolgenden Screenshot sehen Sie das Wochendiagramm für EURUSD im Modus Vertikale Linie mit deaktivierter Umkehrung:
Abb. 1 – Wöchentlicher Timeframe im Modus "Vertikale Linie"
Im nachfolgenden Screenshot sehen Sie den M30-Timeframe im Modus Tag, doch diesmal ist die Umkehrung für Symbole mit USD als Basiswährung aktiviert. In unserem Fall haben wir USDCAD (hellblaue Kerzen) und USDCHF (violette Kerzen).
Abb. 2 – M30-Timeframe im Modus "Tag"
Fazit
Ich denke, wir haben ein ziemlich interessantes und informationsreiches Werkzeug für die mehrwährungsfähige Analyse von Preisunterschieden geschaffen. Dieser Indikator lässt sich unendlich ausbauen und verbessern.
Vielen Dank für Ihre Zeit!
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/754





- 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.