Zeitreihen in der Bibliothek DoEasy (Teil 46): Mehrperioden-Multisymbol-Indikatorpuffer
Inhalt
- Konzept
- Verbesserung der Pufferobjektklassen für die Arbeit mit beliebigen Symbolen
- Test: Mehrperioden-Multisymbol Gleitender Durchschnitt
- Test: Mehrperioden-Multisymbol-MACD
- Was kommt als Nächstes?
Konzept
Zum gegenwärtigen Zeitpunkt enthält die Bibliothek bereits die Funktionalität zur Erstellung und Kontrolle von mehrperioden Indikatorpuffern. Nun müssen wir die Funktionen für die Arbeit im Multisymbolmodus hinzufügen, damit wir weitere Aufgaben bewältigen können, die auf die Entwicklung von Bibliothekswerkzeugen für die Verwendung in kundenspezifischen Programmen abzielen.
Meine frühere Arbeit mit den Pufferobjektklassen bietet bereits eine solche Funktionalität, erfordert aber noch einige Verfeinerungen. Daher werde ich heute die letzten Vorbereitungen treffen und mit der vereinfachten Entwicklung von Multisymbol- und Mehrperioden-Standardindikatoren fortfahren.
Um dies zu erreichen, muss ich die berechnete Pufferobjektklasse so verbessern, dass sie in der Lage ist, die Array-Daten über den Standard-Indikator-Handle in ihr Array aufzunehmen. Die Bibliothek ist dazu bereits in der Lage, aber die kleinen Ergänzungen, die ich gleich implementieren werde, erleichtern die Aufgabe erheblich und ermöglichen die Datenanzeige auf dem aktuellen Chart von Standardindikatoren, die an einem beliebigen Symbol/einer beliebigen Periode platziert sind.
In den folgenden Artikeln werde ich das aktuelle Konzept auf die Klassen für die Arbeit mit Standardindikatordaten von beliebigen Symbolen/Perioden anwenden und die Aufgabe der Erstellung von Multi-Symbol-Multi-Perioden-Standardindikatoren vereinfachen.
Verbesserung der Pufferobjektklassen für die Arbeit mit beliebigen Symbolen
Die Klasse des grundlegendes abstraktes Pufferobjekts enthält die Array-Indexwerte für nachfolgende Indikatorpuffer, die nach dem aktuellen erstellt werden können. In diesem aktuellen Objekt müssen wir auf einfache Berechnungen der Indizes nachfolgender Indikatorpuffer zurückgreifen, die vom Typ des aktuellen Pufferobjekts und seinem Zeichenstil abhängen, wobei die Berechnung auch das Fehlen eines Farbpuffers für den Indikatorpuffer mit dem Zeichenstil "mit Farbe zwischen zwei Ebenen füllen" berücksichtigt.
Um die Aufgabe der Berechnung der Indizes nachfolgender Puffer zu vereinfachen und die rechnerische Klarheit bei der Berücksichtigung verschiedener Faktoren nicht zu beeinträchtigen, können wir eine weitere Klassenvariable einführen, die die Anzahl aller zur Konstruktion des Pufferobjekts verwendeten Arrays enthält. Dieser streng gesetzte Wert wird bei der Erstellung jedes Pufferobjekts (Nachkomme der abstrakten Pufferklasse) angegeben, indem der erforderliche Wert in den Parametern des Konstruktors der abgeleiteten Klasse an den geschützten Basisobjektklassenkonstruktor übergeben wird.
Das sieht verzwickt aus, aber in Wirklichkeit ist alles sehr einfach: Wenn wir ein neues Indikator-Pufferobjekt erzeugen, übergeben wir bereits einige Werte im Pufferobjekt-Klassenkonstruktor an seinen übergeordneten Klassenkonstruktor. Im weiteren Verlauf werden wir einen weiteren Wert übergeben - die Anzahl der Arrays, die für den Aufbau jedes Pufferobjekts erforderlich sind.
Öffnen Sie die Datei der abstrakten Indikatorpuffer-Basisobjektklasse \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh und nehmen Sie die erforderlichen Änderungen daran vor.
Deklarieren Sie die neue Variable im 'private' Teil der Klasse:
//+------------------------------------------------------------------+ //| Abstract indicator buffer class | //+------------------------------------------------------------------+ class CBuffer : public CBaseObj { private: long m_long_prop[BUFFER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[BUFFER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[BUFFER_PROP_STRING_TOTAL]; // String properties bool m_act_state_trigger; // Auxiliary buffer status switch flag uchar m_total_arrays; // Total number of buffer arrays
Wenn der geschützte parametrische Klassenkonstruktor deklariert wird, fügen Sie bei seinen Eingaben eine weitere Variable hinzu. Diese Variable ist zu verwenden, wenn die Anzahl aller Pufferobjekt-Arrays beim Erzeugen dieses Pufferobjekts an die Klasse übergeben wird:
//--- Default constructor CBuffer(void){;} protected: //--- Protected parametric constructor CBuffer(ENUM_BUFFER_STATUS status_buffer, ENUM_BUFFER_TYPE buffer_type, const uint index_plot, const uint index_base_array, const int num_datas, const uchar total_arrays, const int width, const string label); public:
Fügen Sie im Implementierungscode des Konstruktors diese Variable zur Eingabeliste hinzu und weisen Sie der zuvor deklarierten privaten Variable den durch sie hindurchgegangenen Wert zu. Dann benutzen Sie diesen Wert, um den Basis-Array-Index für das nächste Pufferobjekt zu berechnen. Wenn Sie den Index des Farbarrays berechnen, überprüfen Sie den Puffertyp. Wenn es sich um einen zu zeichnenden Puffer handelt, berechnen Sie den Index, indem Sie die Anzahl aller Daten-Arrays zum Basis-Array-Index addieren, während Sie im Falle des Berechnungspuffers Null hinzufügen, da ein Berechnungspuffer kein Farbarray hat:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status, ENUM_BUFFER_TYPE buffer_type, const uint index_plot, const uint index_base_array, const int num_datas, const uchar total_arrays, const int width, const string label) { this.m_type=COLLECTION_BUFFERS_ID; this.m_act_state_trigger=true; this.m_total_arrays=total_arrays; //--- Save integer properties this.m_long_prop[BUFFER_PROP_STATUS] = buffer_status; this.m_long_prop[BUFFER_PROP_TYPE] = buffer_type; ENUM_DRAW_TYPE type= ( !this.TypeBuffer() || !this.Status() ? DRAW_NONE : this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING : ENUM_DRAW_TYPE(this.Status()+8) ); this.m_long_prop[BUFFER_PROP_DRAW_TYPE] = type; this.m_long_prop[BUFFER_PROP_TIMEFRAME] = PERIOD_CURRENT; this.m_long_prop[BUFFER_PROP_ACTIVE] = true; this.m_long_prop[BUFFER_PROP_ARROW_CODE] = 0x9F; this.m_long_prop[BUFFER_PROP_ARROW_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_DRAW_BEGIN] = 0; this.m_long_prop[BUFFER_PROP_SHOW_DATA] = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false); this.m_long_prop[BUFFER_PROP_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_LINE_STYLE] = STYLE_SOLID; this.m_long_prop[BUFFER_PROP_LINE_WIDTH] = width; this.m_long_prop[BUFFER_PROP_COLOR_INDEXES] = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0); this.m_long_prop[BUFFER_PROP_COLOR] = clrRed; this.m_long_prop[BUFFER_PROP_NUM_DATAS] = num_datas; this.m_long_prop[BUFFER_PROP_INDEX_PLOT] = index_plot; this.m_long_prop[BUFFER_PROP_INDEX_BASE] = index_base_array; this.m_long_prop[BUFFER_PROP_INDEX_COLOR] = this.GetProperty(BUFFER_PROP_INDEX_BASE)+ (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0); this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE] = index_base_array+this.m_total_arrays; this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot); //--- Save real properties this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0); //--- Save string properties this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)] = ::Symbol(); this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL); //--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string if(::ArrayResize(this.DataBuffer,(int)this.GetProperty(BUFFER_PROP_NUM_DATAS))==WRONG_VALUE) ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError()); //--- If failed to change the size of the color array (only for a non-calculated buffer), display the appropriate message indicating the string if(this.TypeBuffer()>BUFFER_TYPE_CALCULATE) if(::ArrayResize(this.ArrayColors,(int)this.ColorsTotal())==WRONG_VALUE) ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError()); //--- For DRAW_FILLING, fill in the color array with two default colors if(this.Status()==BUFFER_STATUS_FILLING) { this.SetColor(clrBlue,0); this.SetColor(clrRed,1); } //--- Bind indicator buffers with arrays //--- In a loop by the number of indicator buffers int total=::ArraySize(DataBuffer); for(int i=0;i<total;i++) { //--- calculate the index of the next array and //--- bind the indicator buffer by the calculated index with the dynamic array //--- located by the i loop index in the DataBuffer array int index=(int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i; ::SetIndexBuffer(index,this.DataBuffer[i].Array,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS)); //--- Set indexation flag as in the timeseries to all buffer arrays ::ArraySetAsSeries(this.DataBuffer[i].Array,true); } //--- Bind the color buffer with the array (only for a non-calculated buffer and not for the filling buffer) if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) { ::SetIndexBuffer((int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX); ::ArraySetAsSeries(this.ColorBufferArray,true); } //--- If this is a calculated buffer, all is done if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) return; //--- Set integer parameters of the graphical series ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH)); this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR)); //--- Set real parameters of the graphical series ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE)); //--- Set string parameters of the graphical series ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL)); } //+------------------------------------------------------------------+
Nun werden alle Klassen der abgeleiteten Objekte des abstrakten Puffer-Basisobjektes ergänzt, indem in der Initialisierungsliste der Klassenkonstruktoren die erforderliche Anzahl der für den Aufbau des Puffers verwendeten Arrays übergeben wird.
Für den Array-Puffer (\MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh):
//+------------------------------------------------------------------+ //| Buffer with the "Drawing with arrows" drawing style | //+------------------------------------------------------------------+ class CBufferArrow : public CBuffer { private: public: //--- Constructor CBufferArrow(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Arrows") {}
Für den Linienpuffer (\MQL5\Include\DoEasy\Objects\Indicators\BufferLine.mqh):
//+------------------------------------------------------------------+ //| Buffer of the Line drawing style | //+------------------------------------------------------------------+ class CBufferLine : public CBuffer { private: public: //--- Constructor CBufferLine(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_LINE,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Line") {}
Für den Sektionspuffer (\MQL5\Include\DoEasy\Objects\Indicators\BufferSection.mqh):
//+------------------------------------------------------------------+ //| Buffer of the Section drawing style | //+------------------------------------------------------------------+ class CBufferSection : public CBuffer { private: public: //--- Constructor CBufferSection(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_SECTION,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Section") {}
Für den Histogrammpuffer auf der Nulllinie (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram.mqh):
//+------------------------------------------------------------------+ //| Buffer of the "Histogram from the zero line" drawing style | //+------------------------------------------------------------------+ class CBufferHistogram : public CBuffer { private: public: //--- Constructor CBufferHistogram(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_HISTOGRAM,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,2,"Histogram") {}
Für den Histogrammpuffer aus zwei Indikatorpuffern (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram2.mqh):
//+--------------------------------------------------------------------+ //|Buffer of the "Histogram on two indicator buffers" drawing style | //+--------------------------------------------------------------------+ class CBufferHistogram2 : public CBuffer { private: public: //--- Constructor CBufferHistogram2(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_HISTOGRAM2,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,8,"Histogram2 0;Histogram2 1") {}
Für den ZigZagpuffer (\MQL5\Include\DoEasy\Objects\Indicators\BufferZigZag.mqh):
//+------------------------------------------------------------------+ //|Buffer of the ZigZag drawing style | //+------------------------------------------------------------------+ class CBufferZigZag : public CBuffer { private: public: //--- Constructor CBufferZigZag(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_ZIGZAG,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,1,"ZigZag 0;ZigZag 1") {}
Für die Puffer zum Ausfüllen (\MQL5\Include\DoEasy\Objects\Indicators\BufferFilling.mqh):
//+------------------------------------------------------------------+ //|Buffer of the "Color filling between two levels" drawing style | //+------------------------------------------------------------------+ class CBufferFilling : public CBuffer { private: public: //--- Constructor CBufferFilling(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_FILLING,BUFFER_TYPE_DATA,index_plot,index_base_array,2,2,1,"Filling 0;Filling 1") {}
Für die Puffer zum Zeichnen von Balken (\MQL5\Include\DoEasy\Objects\Indicators\BufferBars.mqh):
//+------------------------------------------------------------------+ //|Buffer of the Bars drawing style | //+------------------------------------------------------------------+ class CBufferBars : public CBuffer { private: public: //--- Constructor CBufferBars(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_BARS,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,2,"Bar Open;Bar High;Bar Low;Bar Close") {}
Für den Puffer zum Zeichnen von Kerzen (\MQL5\Include\DoEasy\Objects\Indicators\BufferCandles.mqh):
//+------------------------------------------------------------------+ //|Buffer of the Candles drawing style | //+------------------------------------------------------------------+ class CBufferCandles : public CBuffer { private: public: //--- Constructor CBufferCandles(const uint index_plot,const uint index_base_array) : CBuffer(BUFFER_STATUS_CANDLES,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,1,"Candle Open;Candle High;Candle Low;Candle Close") {}
Für Berechnungspuffer (\MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh):
//+------------------------------------------------------------------+ //| Calculated buffer | //+------------------------------------------------------------------+ class CBufferCalculate : public CBuffer { private: public: //--- Constructor CBufferCalculate(const uint index_plot,const uint index_array) : CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,1,0,"Calculate") {}
Solche Änderungen befreien uns von der Notwendigkeit, Prüfungen für den Puffertyp und den Zeichenstil durchzuführen, um die Indizes der später erstellten Puffer zu berechnen, da für jeden Puffertyp immer die gleiche Anzahl von Arrays verwendet wird — sie wird in Form von streng spezifizierten Werten beim Erstellen eines Puffers übergeben.
In der Klasse für Berechnungspuffer werden neue Methoden hinzugefügt, um Daten aus dem Standard-Indikator in den Berechnungspuffer zu schreiben:
//+------------------------------------------------------------------+ //| Calculated buffer | //+------------------------------------------------------------------+ class CBufferCalculate : public CBuffer { private: public: //--- Constructor CBufferCalculate(const uint index_plot,const uint index_array) : CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,1,0,"Calculate") {} //--- Supported integer properties of a buffer virtual bool SupportProperty(ENUM_BUFFER_PROP_INTEGER property); //--- Supported real properties of a buffer virtual bool SupportProperty(ENUM_BUFFER_PROP_DOUBLE property); //--- Supported string properties of a buffer virtual bool SupportProperty(ENUM_BUFFER_PROP_STRING property); //--- Display a short buffer description in the journal virtual void PrintShort(void); //--- Set the value to the data buffer array void SetData(const uint series_index,const double value) { this.SetBufferValue(0,series_index,value); } //--- Return the value from the data buffer array double GetData(const uint series_index) const { return this.GetDataBufferValue(0,series_index); } //--- Copy data of the specified indicator to the buffer object array int FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count); int FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count); int FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time); }; //+------------------------------------------------------------------+
Schreiben wir ihre Implementierung außerhalb des Klassenkörpers:
//+------------------------------------------------------------------+ //| Copy data of the specified indicator to the buffer object array | //+------------------------------------------------------------------+ int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count) { return ::CopyBuffer(indicator_handle,buffer_num,start_pos,count,this.DataBuffer[0].Array); } //+------------------------------------------------------------------+ int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count) { return ::CopyBuffer(indicator_handle,buffer_num,start_time,count,this.DataBuffer[0].Array); } //+------------------------------------------------------------------+ int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time) { return ::CopyBuffer(indicator_handle,buffer_num,start_time,stop_time,this.DataBuffer[0].Array); } //+------------------------------------------------------------------+
Alle drei Methoden verwenden drei Varianten der überladenen Funktion CopyBuffer(). Als Empfangsarray wird das durch den entsprechenden Indikatorpuffer zugewiesene Array verwendet. Der passende Indikatorpuffer ist derjenige, von dem aus die Methoden zum Schreiben der erforderlichen Indikatordaten über seinen Handle in das Objektarray aufgerufen werden.
Nun wollen wir den Multisymbolmodus für die Arbeit mit Pufferobjekten implementieren. Zunächst muss ich auf einige meiner Annahmen eingehen, die ich bei der Vorbereitung des Materials für den vorherigen Artikel gemacht habe, in dem ich den Mehrperiodenmodus implementiert habe.
In der Klasse für die Pufferkollektion des Indikators habe ich die Methode zum Empfang der erforderlichen Zeitreihen- und Balken-Daten für die Arbeit mit einem einzelnen Pufferbalken erstellt. Diese Methode enthält alle notwendigen Daten zu Diagrammperioden — das aktuelle und zugeordnete Pufferobjekt, sowie alle notwendigen Daten zu Symbolen - das aktuelle und zugeordnete Pufferobjekt. Unten ist die Methode aus dem vorherigen Artikel:
//+------------------------------------------------------------------+ //| Get data of the necessary timeseries and bars | //| for working with a single bar of the buffer | //+------------------------------------------------------------------+ int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period) { //--- Get timeseries of the current chart and the chart of the buffer timeframe CSeriesDE *series_current=this.m_timeseries.GetSeries(buffer.Symbol(),PERIOD_CURRENT); CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe()); if(series_current==NULL || series_period==NULL) return WRONG_VALUE; //--- Get the bar object of the current timeseries corresponding to the required timeseries index CBar *bar_current=series_current.GetBar(series_index); if(bar_current==NULL) return WRONG_VALUE; //--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe()); if(bar_period==NULL) return WRONG_VALUE; //--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart index_bar_period=bar_period.Index(PERIOD_CURRENT); //--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period //--- and return this value (1 if the result is 0) int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe()); return(num_bars>0 ? num_bars : 1); } //+------------------------------------------------------------------+
Hier habe ich versäumt, Daten aus dem notwendigen Symbol in nur zwei Zeichenfolgen zu erhalten — für die aktuelle Zeitreihe des Charts erhielten wir die Daten aus dem Charts des dem Pufferobjekt zugewiesenen Symbols. In der zweiten Zeichenfolge ist der Fall genau umgekehrt: wir erhalten das aktuelle Chartsymbol an der Stelle, an der wir das Symbol des Pufferobjekts nehmen müssen.
Infolgedessen laufen alle Korrekturen auf nur zwei Korrekturen in den beiden Codezeilen hinaus.
Die vollständige Auflistung der korrigierten Methode:
//+------------------------------------------------------------------+ //| Get data of the necessary timeseries and bars | //| for working with a single bar of the buffer | //+------------------------------------------------------------------+ int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period) { //--- Get timeseries of the current chart and the chart of the buffer timeframe CSeriesDE *series_current=this.m_timeseries.GetSeries(Symbol(),PERIOD_CURRENT); CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe()); if(series_current==NULL || series_period==NULL) return WRONG_VALUE; //--- Get the bar object of the current timeseries corresponding to the required timeseries index CBar *bar_current=series_current.GetBar(series_index); if(bar_current==NULL) return WRONG_VALUE; //--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),buffer.Symbol(),series_period.Timeframe()); if(bar_period==NULL) return WRONG_VALUE; //--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart index_bar_period=bar_period.Index(PERIOD_CURRENT); //--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period //--- and return this value (1 if the result is 0) int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe()); return(num_bars>0 ? num_bars : 1); } //+------------------------------------------------------------------+
Alles ist eingestellt. Jetzt können unsere Pufferobjekte auch im Multisymbolmodus arbeiten.
Uns fehlt noch die Methode, die den Balkenindex auf einem Symbol/Periodendiagramm zurückgibt, in das der Index des angegebenen Balkens des aktuellen Charts fällt. Die Methode ist für die korrekte Anzeige von Daten aus einem anderen Symbol/einer anderen Periode im aktuellen Chart während der Hauptindikatorschleife erforderlich.
Der geeignetste Ort für eine solche Methode ist die Klasse der Zeitreihenkollektion \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
Deklarieren wir darin die neue Methode:
//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time //--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true); CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); //--- Return the bar index on the specified timeframe chart by the current chart's bar index | int IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0);
Schreiben wir seine Implementierung außerhalb des Klassenkörpers:
//+------------------------------------------------------------------+ //| Return the bar index on the specified timeframe chart | //| by the current chart's bar index | //+------------------------------------------------------------------+ int CTimeSeriesCollection::IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.GetSeries(::Symbol(),(ENUM_TIMEFRAMES)::Period()); if(series==NULL) return WRONG_VALUE; CBar *bar=series.GetBar(series_index); if(bar==NULL) return WRONG_VALUE; return ::iBarShift(symbol,timeframe,bar.Time()); } //+------------------------------------------------------------------+
Die Methode erhält den aktuellen Balkenindex des Charts, sowie Symbol und Periode des Charts, für das der Balkenindex, der dem Zeitpunkt des aktuellen Chartindexes entspricht, der an die Methode übergeben wurde, zurückgegeben werden soll.
Weiterhin erhalten wir mit den Zeiger auf die aktuelle Chartzeitreihe, den Zeiger auf das Balkenobjekt durch den aktuellen Zeitreihenindex und benutzen die Balkenzeit um den Index des entsprechenden Balkens auf die gewünschte Zeitreihe zurückzugeben.
Da der Berechnungspuffer die Daten des Indikatorpuffers in voller Übereinstimmung mit der Zeitreihe, auf der der Indikator basiert, speichern soll, wird diese Methode verwendet, um den Index im Berechnungspuffer zu erhalten, der dem angegebenen Balkenindex auf dem aktuellen Chart entspricht (der Indikatorschleifenindex kann hier als praktisches Beispiel verwendet werden). Wenn wir eine solche Übereinstimmung zwischen zwei verschiedenen Zeitreihen herstellen können, dann können wir diese Daten korrekt auf dem erforderlichen Chart anzeigen.
Um von nutzerdefinierten Programmen aus auf die Methode zuzugreifen, müssen wir den Zugriff auf die Methode über die Bibliothek der Hauptobjektklasse CEngine (\MQL5\Include\DoEasy\Engine.mqh) ermöglichen:
//--- Clear data by the timeseries index for the (1) arrow, (2) line, (3) section, (4) zero line histogram, //--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles void BufferArrowClear(const int number,const int series_index) { this.m_buffers.ClearBufferArrow(number,series_index); } void BufferLineClear(const int number,const int series_index) { this.m_buffers.ClearBufferLine(number,series_index); } void BufferSectionClear(const int number,const int series_index) { this.m_buffers.ClearBufferSection(number,series_index); } void BufferHistogramClear(const int number,const int series_index) { this.m_buffers.ClearBufferHistogram(number,series_index); } void BufferHistogram2Clear(const int number,const int series_index) { this.m_buffers.ClearBufferHistogram2(number,series_index);} void BufferZigZagClear(const int number,const int series_index) { this.m_buffers.ClearBufferZigZag(number,series_index); } void BufferFillingClear(const int number,const int series_index) { this.m_buffers.ClearBufferFilling(number,series_index); } void BufferBarsClear(const int number,const int series_index) { this.m_buffers.ClearBufferBars(number,series_index); } void BufferCandlesClear(const int number,const int series_index) { this.m_buffers.ClearBufferCandles(number,series_index); } //--- Return the bar index on the specified timeframe chart by the current chart's bar index int IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe) { return this.m_time_series.IndexBarPeriodByBarCurrent(series_index,symbol,timeframe); } //--- Display short description of all indicator buffers of the buffer collection void BuffersPrintShort(void);
Damit ist die Verbesserung der Bibliotheksklassen zur Erprobung der Entwicklung und Handhabung von Multisymbol- und Mehrperioden-Indikatoren.
Um den Test durchzuführen, erstellen wir zwei Multisymbol-Mehrperioden-Indikatoren — einen Gleitenden Durchschnitt und den MACD, die ihre Daten von einem bestimmten Symbol/einer bestimmten Periode auf dem aktuellen Chart beziehen. Wir stellen in den Indikatoreinstellungen die Parameter für den Indikator und das Symbol für die Chart-Periode ein, aus denen die Daten des Standardindikators gewonnen werden sollen.
Test: Mehrperioden-Multisymbol Gleitender Durchschnitt
Um den Test durchzuführen, nehmen wir den Indikator aus dem vorherigen Artikel und speichern ihn in \MQL5\Indikatoren\TestDoEasy\Teil46\ als TestDoEasyPart46_1.mq5.
Der Indikator soll die Kerzen des in den Einstellungen angegebenen Symbols und der Periode in einem separaten Unterfenster anzeigen. Der Gleitende Durchschnitt mit den angegebenen Parametern und dem gleichen Symbol/der gleichen Periode soll im gleichen Unterfenster angezeigt werden.
Wir legen für die Indikatordaten das Chart-Unterfenster fest, stellen Symbolwerte und Chart-Periode für den Indikator, sowie die Eingaben für den Gleitenden Durchschnitt ein. Außerdem setzen wir die globalen Variablen zur Anpassung eingegebener MA-Parameter:
//+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- properties #property indicator_separate_window #property indicator_buffers 8 #property indicator_plots 2 //--- classes //--- enums //--- defines //--- structures //--- input variables sinput string InpUsedSymbols = "GBPUSD"; // Used symbol (one only) sinput ENUM_TIMEFRAMES InpPeriod = PERIOD_M30; // Used chart period //--- sinput uint InpPeriodMA = 14; // MA Period sinput int InpShiftMA = 0; // MA Shift sinput ENUM_MA_METHOD InpMethodMA = MODE_SMA; // MA Method sinput ENUM_APPLIED_PRICE InpPriceMA = PRICE_CLOSE; // MA Applied Price //--- sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers CArrayObj *list_buffers; // Pointer to the buffer object list //--- global variables ENUM_SYMBOLS_MODE InpModeUsedSymbols= SYMBOLS_MODE_DEFINES; // Mode of used symbols list ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list string InpUsedTFs; // List of used timeframes CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names int min_bars; // The minimum number of bars for the indicator calculation int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // The array for passing used symbols to the library string array_used_periods[]; // The array for passing used timeframes to the library int handle_ma; // МА handle int period_ma; // Moving Average calculation period //+------------------------------------------------------------------+
In OnInit() erstellen wir drei Pufferobjekte — das erste ist für die МА-Linie, das zweite ist für die ausgewählten Symbolkerzen, während das dritte zum Speichern der berechneten Daten des gleitenden Durchschnitts dient, die aus dem ausgewählten Symbol/der ausgewählten Periode erhalten wurden.
Als Nächstes setzen wir die Beschreibungen aller vier Datenfenster-Puffer-Arrays für den Kerzenpuffer. Machen wir dasselbe auch für den MA-Linienpuffer. Erstellen wir nach Abschluss das Handle des Indikators für den gleitenden Durchschnitt:
.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); //--- Initialize DoEasy library OnInitDoEasy(); IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+1); //--- Set indicator global variables prefix=engine.Name()+"_"; //--- Get the index of the maximum used timeframe in the array, //--- calculate the number of bars of the current period fitting in the maximum used period //--- Use the obtained value if it exceeds 2, otherwise use 2 int index=ArrayMaximum(ArrayUsedTimeframes); int num_bars=NumberBarsInTimeframe(ArrayUsedTimeframes[index]); min_bars=(index>WRONG_VALUE ? (num_bars>2 ? num_bars : 2) : 2); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- indicator buffers mapping //--- Create all the necessary buffer objects engine.BufferCreateLine(); // 2 arrays engine.BufferCreateCandles(); // 5 arrays engine.BufferCreateCalculate(); // 1 array //--- Check the number of buffers specified in the 'properties' block if(engine.BuffersPropertyPlotsTotal()!=indicator_plots) Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal()); if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers) Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal()); //--- Create the color array and set non-default colors to all buffers within the collection color array_colors[]={clrDodgerBlue,clrRed,clrGray}; engine.BuffersSetColors(array_colors); //--- Set МА period period_ma=int(InpPeriodMA<2 ? 2 : InpPeriodMA); //--- In a loop by the list of collection buffer objects, for(int i=0;i<engine.GetListBuffers().Total();i++) { //--- get the next buffer CBuffer *buff=engine.GetListBuffers().At(i); if(buff==NULL) continue; //--- and set its display in the data window depending on its specified usage //--- and also a chart period and symbol selected in the settings buff.SetShowData(true); buff.SetTimeframe(InpPeriod); buff.SetSymbol(InpUsedSymbols); if(buff.Status()==BUFFER_STATUS_CANDLES) { string pr=InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" "; string label=pr+"Open;"+pr+"High;"+pr+"Low;"+pr+"Close"; buff.SetLabel(label); } if(buff.Status()==BUFFER_STATUS_LINE) { string label="MA("+(string)period_ma+")"; buff.SetLabel(label); } } //--- Display short descriptions of created indicator buffers engine.BuffersPrintShort(); //--- Create МА handle handle_ma=iMA(InpUsedSymbols,InpPeriod,period_ma,InpShiftMA,InpMethodMA,InpPriceMA); if(handle_ma==INVALID_HANDLE) return INIT_FAILED; //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Kopieren Sie im Datenvorbereitungsblock von OnCalculate() die MA-Daten in den Berechnungspuffer.
Um das Kopieren des gesamten MA-Datenarrays bei jedem Tick zu vermeiden, müssen wir bedenken, dass die Variable "limit" so berechnet wird, dass sie beim ersten Start oder beim Ändern von Verlaufsdaten 1 überschreitet, beim Öffnen eines neuen Balkens gleich 1 ist und den Rest der Zeit bei jedem Tick gleich 0.
Wir können keine Daten kopieren, die auf dem Wert von 'limit' basieren — es ist unmöglich, den nullten Balken zu kopieren. Das bedeutet, dass wir einen Balken kopieren müssen, wenn 'limit' Null ist. In anderen Fällen kopieren wir so viele, wie im 'limit' angegeben. Daher habe ich ein ressourcenschonendes Kopieren der relevanten MA-Daten in den Berechnungspuffer veranlasst:
//--- Prepare data CBufferCalculate *buff_calc=engine.GetBufferCalculate(0); int total_copy=(limit<2 ? 1 : limit); int copied=buff_calc.FillAsSeries(handle_ma,0,0,total_copy); if(copied<total_copy) return 0;
In der Hauptindikatorschleife löschen wir zuerst den aktuellen Balken aller zu zeichnenden Indikatorpuffer (um Zufallswerte loszuwerden) und berechnen dann die MA-Linie und die Kerzen des ausgewählten Symbols neu, während wir ihre Darstellung im aktuellen Chart neu berechnen:
//--- Main calculation loop of the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { //--- Clear the current bar of all created buffers engine.BufferLineClear(0,0); engine.BufferCandlesClear(0,0); //--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]); if(bar==NULL) continue; //--- Calculate the color index depending on the candle direction on the timeframe specified in the settings color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2); //--- Calculate the MA line buffer int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod); if(index<0) continue; engine.BufferSetDataLine(0,i,buff_calc.GetData(index),color_index); //--- Calculate the candle buffer engine.BufferSetDataCandles(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index); } //--- return value of prev_calculated for next call
Der vollständige Indikatorcode ist in den unten angehängten Dateien enthalten.
Starten Sie den Indikator auf EURUSD M15, indem Sie GBPUSD M30 und einen einfachen gleitenden Durchschnitt nach Schlusskursen mit der Periode von 14 und der Verschiebung von 0 angeben:
Zum Vergleich wurde das GBPUSD-Chart mit dem Indikator des gleitenden Durchschnitts mit den gleichen Parametern geöffnet.
Test: Mehrperioden-Multisymbol-MACD
Lassen Sie uns nun einen Multisymbol-MACD mit mehreren Perioden erstellen. Speichern Sie den neu erstellten Indikator als TestDoEasyPart46_2.mq5.
Stellen Sie die Eingänge für den MACD und alle notwendigen Puffer für seine Berechnung und Anzeige ein. Wir benötigen zwei zu zeichnende Puffer: Histogramm und Linie zur Anzeige des MACD auf dem aktuellen Chart und zwei Berechnungspuffer zur Speicherung der Histogrammdaten und der MACD-Signallinie, die aus dem in den Einstellungen angegebenen Symbol/der Periode erhalten werden.
Ich habe versucht, alle Aktionen und die Logik in den Kommentaren zum Code detailliert zu beschreiben, daher werde ich hier nur Grundveränderungen im Vergleich zum vorherigen Indikator erwähnen:
//+------------------------------------------------------------------+ //| TestDoEasyPart46_2.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- properties #property indicator_separate_window #property indicator_buffers 6 #property indicator_plots 2 //--- classes //--- enums //--- defines //--- structures //--- input variables sinput string InpUsedSymbols = "GBPUSD"; // Used symbol (one only) sinput ENUM_TIMEFRAMES InpPeriod = PERIOD_M30; // Used chart period //--- sinput uint InpPeriodFastEMA = 12; // MACD Fast EMA Period sinput uint InpPeriodSlowEMA = 26; // MACD Slow EMA Period sinput uint InpPeriodSignalMA = 9; // MACD Signal MA Period sinput ENUM_APPLIED_PRICE InpPriceMACD = PRICE_CLOSE; // MA Applied Price //--- sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers CArrayObj *list_buffers; // Pointer to the buffer object list //--- global variables ENUM_SYMBOLS_MODE InpModeUsedSymbols= SYMBOLS_MODE_DEFINES; // Mode of used symbols list ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list string InpUsedTFs; // List of used timeframes CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names int min_bars; // The minimum number of bars for the indicator calculation int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // The array for passing used symbols to the library string array_used_periods[]; // The array for passing used timeframes to the library int handle_macd; // МАCD handle int fast_ema_period; // Fast EMA calculation period int slow_ema_period; // Slow EMA calculation period int signal_period; // MACD signal line calculation period //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); //--- Initialize DoEasy library OnInitDoEasy(); IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+1); //--- Set indicator global variables prefix=engine.Name()+"_"; //--- calculate the number of bars of the current period fitting in the maximum used period //--- Use the obtained value if it exceeds 2, otherwise use 2 int num_bars=NumberBarsInTimeframe(InpPeriod); min_bars=(num_bars>2 ? num_bars : 2); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- indicator buffers mapping //--- Create all the necessary buffer objects for constructing MACD engine.BufferCreateHistogram(); // 2 arrays engine.BufferCreateLine(); // 2 arrays engine.BufferCreateCalculate(); // 1 array for MACD histogram data from the specified symbol/period engine.BufferCreateCalculate(); // 1 array for MACD signal line from the specified symbol/period //--- Check the number of buffers specified in the 'properties' block if(engine.BuffersPropertyPlotsTotal()!=indicator_plots) Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal()); if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers) Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal()); //--- Create the color array and set non-default colors to all buffers within the collection color array_colors[]={clrDodgerBlue,clrRed,clrGray}; engine.BuffersSetColors(array_colors); //--- Set МАCD calculation periods fast_ema_period=int(InpPeriodFastEMA<1 ? 1 : InpPeriodFastEMA); slow_ema_period=int(InpPeriodSlowEMA<1 ? 1 : InpPeriodSlowEMA); signal_period=int(InpPeriodSignalMA<1 ? 1 : InpPeriodSignalMA); //--- Get the histogram buffer (the first drawn buffer) //--- It has the index of 0 considering that the starting point is zero CBufferHistogram *buff_hist=engine.GetBufferHistogram(0); if(buff_hist!=NULL) { //--- Set the line width for the histogram buff_hist.SetWidth(3); //--- Set the graphical series description for the histogram string label="MACD ("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")"; buff_hist.SetLabel(label); //--- and set display in the data window for the buffer //--- and also a chart period and symbol selected in the settings buff_hist.SetShowData(true); buff_hist.SetTimeframe(InpPeriod); buff_hist.SetSymbol(InpUsedSymbols); } //--- Get the signal line buffer (the first drawn buffer) //--- It has the index of 0 considering that the starting point is zero CBufferLine *buff_line=engine.GetBufferLine(0); if(buff_line!=NULL) { //--- Set the signal line width buff_line.SetWidth(1); //--- Set the graphical series description for the signal line string label="Signal"; buff_line.SetLabel(label); //--- and set display in the data window for the buffer //--- and also a chart period and symbol selected in the settings buff_line.SetShowData(true); buff_line.SetTimeframe(InpPeriod); buff_line.SetSymbol(InpUsedSymbols); } //--- Get the first calculated buffer //--- It has the index of 0 considering that the starting point is zero CBufferCalculate *buff_calc=engine.GetBufferCalculate(0); if(buff_calc!=NULL) { //--- Set the description of the first calculated buffer as the "MACD histogram temporary array"" buff_calc.SetLabel("MACD_HIST_TMP"); //--- and set a chart period and symbol selected in the settings for it buff_calc.SetTimeframe(InpPeriod); buff_calc.SetSymbol(InpUsedSymbols); } //--- Get the second calculated buffer //--- It has the index of 1 considering that the starting point is zero buff_calc=engine.GetBufferCalculate(1); if(buff_calc!=NULL) { //--- Set the description of the second calculated buffer as the "MACD signal line temporary array"" buff_calc.SetLabel("MACD_SIGN_TMP"); //--- and set a chart period and symbol selected in the settings for it buff_calc.SetTimeframe(InpPeriod); buff_calc.SetSymbol(InpUsedSymbols); } //--- Display short descriptions of created indicator buffers engine.BuffersPrintShort(); //--- Create МАCD handle handle_macd=iMACD(InpUsedSymbols,InpPeriod,fast_ema_period,slow_ema_period,signal_period,InpPriceMACD); if(handle_macd==INVALID_HANDLE) return INIT_FAILED; //--- IndicatorSetString(INDICATOR_SHORTNAME,InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" MACD("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")"); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove indicator graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Check for the minimum number of bars for calculation if(rates_total<min_bars || Point()==0) return 0; //--- Handle the Calculate event in the library //--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick if(engine.0) return 0; //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the library timer EventsHandling(); // Working with library events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Check and calculate the number of calculated bars //--- If limit = 0, there are no new bars - calculate the current one //--- If limit = 1, a new bar has appeared - calculate the first and the current ones //--- limit > 1 means the first launch or changes in history - the full recalculation of all data int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; engine.BuffersInitPlots(); engine.BuffersInitCalculates(); } //--- Prepare data int total_copy=(limit<2 ? 1 : limit); //--- Get the first calculated buffer by its number CBufferCalculate *buff_calc_hist=engine.GetBufferCalculate(0); //--- Fill in the first calculated buffer with MACD histogram data int copied=buff_calc_hist.FillAsSeries(handle_macd,0,0,total_copy); if(copied<total_copy) return 0; //--- Get the second calculated buffer by its number CBufferCalculate *buff_calc_sig=engine.GetBufferCalculate(1); //--- Fill in the second calculated buffer with MACD signal line data copied=buff_calc_sig.FillAsSeries(handle_macd,1,0,total_copy); if(copied<total_copy) return 0; //--- Calculate the indicator CBar *bar=NULL; // Bar object for defining the candle direction uchar color_index=0; // Color index to be set for the buffer depending on the candle direction //--- Main calculation loop of the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { //--- Clear the current bar of all created buffers engine.BufferHistogramClear(0,0); engine.BufferLineClear(0,0); //--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]); if(bar==NULL) continue; //--- Calculate the color index depending on the candle direction on the timeframe specified in the settings color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2); //--- Calculate the MACD histogram buffer int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod); if(index<0) continue; engine.BufferSetDataHistogram(0,i,buff_calc_hist.GetData(index),color_index); //--- Calculate MACD signal line buffer engine.BufferSetDataLine(0,i,buff_calc_sig.GetData(index),color_index); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Die Farben der Histogramm- und Signallinienspalten auf jedem Balken entsprechen der Kerzenrichtung eines Symbols/einer Periode, auf der der MACD basiert:
Der Indikator wird auf EURUSD M15 mit den Standardeinstellungen des MACD basierend auf GBPUSD M30 gestartet. Der GBPUSD M30 Chart mit dem Standard-MACD mit den gleichen Parametern wird zur besseren Übersichtlichkeit geöffnet.
Was kommt als Nächstes?
Im nächsten Artikel erhält die Bibliothek die Funktionalität, die das Erstellen von Multisymbol- und Mehrperioden-Standardindikatoren erleichtert.
Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie testen und herunterladen können.
Hinterlassen Sie Ihre Fragen, Kommentare und Anregungen in den Kommentaren.
Bitte bedenken Sie, dass ich hier den MQL5-Testindikator für MetaTrader 5 entwickelt habe.
Die angehängten Dateien sind nur für MetaTrader 5 bestimmt. Die aktuelle Bibliotheksversion wurde nicht mit dem MetaTrader 4 getestet.
Nachdem ich die Funktionalität für die Arbeit mit Indikatorpuffern entwickelt und getestet habe, werde ich versuchen, einige MQL5-Funktionen in MetaTrader 4 zu implementieren.
Frühere Artikel dieser Serie:
Zeitreihen in der Bibliothek DoEasy (Teil 35): das Balkenobjekt und die Liste der Zeitreihen eines SymbolsZeitreihen in der Bibliothek DoEasy (Teil 36): Objekt der Zeitreihe für alle verwendeten Symbolzeitrahmen
Zeitreihen in der Bibliothek DoEasy (Teil 37): Kollektion von Zeitreihen - Datenbank der Zeitreihen nach Symbolen und Zeitrahmen
Zeitreihen in der Bibliothek DoEasy (Teil 38): Kollektion von Zeitreihen - Aktualisierungen in Echtzeit und Datenzugriff aus dem Programm
Zeitreihen in der Bibliothek DoEasy (Teil 40): Bibliotheksbasierte Indikatoren - Aktualisierung der Daten in Echtzeit
Zeitreihen in der Bibliothek DoEasy (Teil 41): Beispiel eines Multi-Symbol- und Multi-Zeitrahmen-Indikators
Zeitreihen in der Bibliothek DoEasy (Teil 42): Abstrakte Objektklasse der Indikatorpuffer
Zeitreihen in der Bibliothek DoEasy (Teil 43): Klassen der Objekte von Indikatorpuffern
Zeitreihen in der Bibliothek DoEasy (Teil 44): Kollektionsklasse der Objekte von Indikatorpuffern
<Zeitreihen in der Bibliothek DoEasy (Teil 45): Puffer für Mehrperiodenindikator
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/8115
- 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.