Zeitreihen in der Bibliothek DoEasy (Teil 38): Kollektion von Zeitreihen - Aktualisierungen in Echtzeit und Datenzugriff aus dem Programm
Artyom Trishkin | 7 Juli, 2020
Inhalt
- Konzept
- Verbesserung der Zeitreihenklassen
- Klasse "New tick" und Datenaktualisierung
- Tests
- Was kommt als Nächstes?
Konzept
In den vorangegangenen Artikeln, die sich der Erstellung von Zeitreihen beliebiger Chartperioden und beliebiger Symbole widmeten, haben wir eine vollwertige Klasse der Zeitreihenkollektion aller im Programm verwendeten Symbole erstellt und gelernt, Zeitreihen für ihre schnelle Suche und Sortierung mit historischen Daten zu füllen.
Ein solches Mittel wird es uns ermöglichen, verschiedene Kombinationen von Preisdaten in der Historie zu suchen und zu vergleichen. Aber wir müssen auch über eine Aktualisierung der aktuellen Daten nachdenken, die bei jedem neuen Tick für jedes verwendete Symbol erfolgen sollte.
Selbst die einfachste Version erlaubt es uns, alle Zeitreihen in der Funktion OnTimer() in einer Millisekunden-Frequenz des Programms zu aktualisieren. Dies wirft jedoch die Frage auf, ob die Zeitreihendaten immer genau nach dem Timer-Zähler aktualisiert werden sollen. Schließlich werden die Daten bei Ankunft eines neuen Ticks im Programm geändert. Es wäre falsch, die Daten einfach unabhängig vom Eintreffen eines neuen Ticks zu aktualisieren — das wäre leistungsmäßig irrational.
Während wir immer über die Ankunft eines neuen Ticks in OnTick() eines EAs oder in OnCalculate() bei einem Indikator auf dem aktuellen Symbol erkennen, ist dies bei keinem anderen Symbol der Fall, das von dem auf einem anderen Symbol gestarteten Programm verfolgt wird. Diese Aufgabe erfordert die gesonderte Verfolgung der notwendigen Ereignisse in einem EA oder einem Indikator.
Hier ist die einfachste mögliche Option, die den aktuellen Bibliotheksbedarf befriedigt, der Vergleich der vorherigen Tick-Zeit mit der aktuellen. Wenn die vorangegangene Tick-Zeit von der aktuellen abweicht, wird ein neuer Tick als auf einem Symbol angekommen betrachtet, das vom Programm verfolgt wird, aber nicht "nativ" für dieses Symbol ist (das Programm wird auf dem Chart eines anderen Symbols gestartet).
Lassen Sie uns die bestehenden Klassen leicht verbessern, bevor wir die Klasse "New tick" und die Echtzeit-Aktualisierung aller im Programm verwendeten Zeitreihen entwickeln.
Verbesserung der Zeitreihenklassen
Zunächst erhält die Datei Datas.mqh den Index neue Nachricht der Bibliothek :
//--- CTimeSeries MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL, // First, set a symbol using SetSymbol() MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE, // Timeseries is not used. Set the flag using SetAvailable() MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME, // Unknown timeframe
und der Nachrichtentext entsprechend dem neu hinzugefügten Index:
{"Сначала нужно установить символ при помощи SetSymbol()","First you need to set Symbol using SetSymbol()"}, {"Таймсерия не используется. Нужно установить флаг использования при помощи SetAvailable()","Timeseries not used. Need to set usage flag using SetAvailable()"}, {"Неизвестный таймфрейм","Unknown timeframe"},
Die Klasse CBaseObj des Basisobjekts aller Bibliotheksobjekte verfügt über zwei Variablen:
//+------------------------------------------------------------------+ //| Base object class for all library objects | //+------------------------------------------------------------------+ class CBaseObj : public CObject { protected: ENUM_LOG_LEVEL m_log_level; // Logging level ENUM_PROGRAM_TYPE m_program; // Program type bool m_first_start; // First launch flag bool m_use_sound; // Flag of playing the sound set for an object bool m_available; // Flag of using a descendant object in the program int m_global_error; // Global error code long m_chart_id_main; // Control program chart ID long m_chart_id; // Chart ID string m_name; // Object name string m_folder_name; // Name of the folder storing CBaseObj descendant objects string m_sound_name; // Object sound file name int m_type; // Object type (corresponds to the collection IDs) public:
Die Variable m_chart_id_main speichert die Kontrollprogramm Chart-ID — dies ist die des Charts mit dem Symbol, auf dem das Programm gestartet wurde. Das Chart soll alle in den Kollektionen und Objekten der Bibliothek registrierten Ereignisse erhalten.
Die m_chart_id speichert die ID des Charts, zu dem das von der Klasse CBaseObj abgeleitete Objekt irgendwie in Beziehung steht. Diese Eigenschaft wird noch nirgendwo verwendet. Das kommt später.
Da wir die Variable m_chart_id_main später als m_chart_id hinzugefügt haben, werden alle Nachrichten an die in der Variable m_chart_id festgelegte Chart-ID gesendet. Ich habe dies behoben. Jetzt sind alle aktuellen Chart-IDs in der Variablen m_chart_id_main festgelegt. Alle Klassen, die Nachrichten von der Bibliothek an das Kontrollprogramm auf dem Chart senden, wurden geändert — alle Instanzen von "m_chart_id" wurden durch "m_chart_id_main" ersetzt.
Solche Änderungen wurden an allen Ereignisklassen aus dem Ordner \MQL5\Include\DoEasy\Objects\Events\ sowie an den Dateien der Kollektionsklassen AccountsCollection.mqh, EventsCollection.mqh und SymbolsCollection.mqh vorgenommen.
Sie können alle Änderungen in den angehängten Dateien sehen.
Um die Daten des angegebenen Balkens aus der Zeitreihenkollektion anzuzeigen, fügen Sie die Textbeschreibung der Balkenparameter der Klasse CBar der Datei \MQL5\Include\DoEasy\Objects\Series\Bar.mqh hinzu.
In dem Codeblock, der die Beschreibung der Objekteigenschaften enthält, deklarieren Sie die Methode zur Erstellung der Textbeschreibung der Balkenparameter:
//+------------------------------------------------------------------+ //| Descriptions of bar object properties | //+------------------------------------------------------------------+ //--- Get description of a bar's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_BAR_PROP_INTEGER property); string GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property); string GetPropertyDescription(ENUM_BAR_PROP_STRING property); //--- Return the bar type description string BodyTypeDescription(void) const; //--- Send description of bar properties to the journal (full_prop=true - all properties, false - only supported ones) void Print(const bool full_prop=false); //--- Display a short bar description in the journal virtual void PrintShort(void); //--- Return the (1) short name and (2) description of bar object parameters virtual string Header(void); string ParameterDescription(void); //--- }; //+------------------------------------------------------------------+
Außerhalb des Klassenkörpers implementieren wir die die Methode zur Erstellung der Textbeschreibung der Balkenparameter und die geänderte Implementierung der Methode zur Anzeige der kurzen Balkenbeschreibung im Journal:
//+------------------------------------------------------------------+ //| Return the description of the bar object parameters | //+------------------------------------------------------------------+ string CBar::ParameterDescription(void) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( ::TimeToString(this.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+", "+ "O: "+::DoubleToString(this.Open(),dg)+", "+ "H: "+::DoubleToString(this.High(),dg)+", "+ "L: "+::DoubleToString(this.Low(),dg)+", "+ "C: "+::DoubleToString(this.Close(),dg)+", "+ "V: "+(string)this.VolumeTick()+", "+ (this.VolumeReal()>0 ? "R: "+(string)this.VolumeReal()+", " : "")+ this.BodyTypeDescription() ); } //+------------------------------------------------------------------+ //| Display a short bar description in the journal | //+------------------------------------------------------------------+ void CBar::PrintShort(void) { ::Print(this.Header(),": ",this.ParameterDescription()); } //+------------------------------------------------------------------+
Hier habe ich einfach den Parameterbeschreibungscode aus der Methode entfernt, die die Balkenparameter im Journal anzeigt, und ihn in die neue Methode eingefügt, die die Textnachricht zurückgibt. Bei dem Ausdruck der Balkenparameter im Journal zeigen wir die zusammengesetzte Nachricht bestehend aus einem kurzen Balkenobjektnamen und seinen Parametern an, deren Textbeschreibung jetzt in der neuen Methode ParameterDescription() generiert wird.
Um die "nicht-originären" Zeitreihen (das sind nicht die, auf der das Programm gestartet wird) zu aktualisieren, haben wir uns entschieden, die Klasse "New tick" zu erstellen und die Daten solcher Symbole erst beim Eintreffen des Ereignisses "New tick" für jedes im Programm verwendete Symbol zu aktualisieren.
Klasse "New tick" und Datenaktualisierung
Erstellen Sie im Ordner \MQL5\Include\DoEasy\Objects\ den Ordner Ticks\ mit der Datei NewTickObj.mqh der Klasse CNewTickObj, abgeleitet vom Basisobjekt aller CBaseObj-Bibliotheksobjekte (wessen die Datei in die Klassendatei aufgenommen wird) und tragen Sie die erforderlichen Daten ein:
//+------------------------------------------------------------------+ //| NewTickObj.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| "New tick" class | //+------------------------------------------------------------------+ class CNewTickObj : public CBaseObj { private: MqlTick m_tick; // Structure of the current prices MqlTick m_tick_prev; // Structure of the current prices during the previous check string m_symbol; // Object symbol bool m_new_tick; // New tick flag public: //--- Set a symbol void SetSymbol(const string symbol) { this.m_symbol=symbol; } //--- Return the new tick flag bool IsNewTick(void); //--- Update price data in the tick structure and set the "New tick" event flag if necessary void Refresh(void) { this.m_new_tick=this.IsNewTick(); } //--- Constructors CNewTickObj(void){;} CNewTickObj(const string symbol); }; //+------------------------------------------------------------------+
Die Variable m_tick speichert die Preisdaten des letzten angekommenen Tick.
Die Variable m_tick_prev speichert die Preisdaten des letzten Ticks.
Die Variable m_symbol speichert ein Symbol, dessen neuer Tick verfolgt werden soll.
Das neue Tick-Flag in der Variablen m_new_tick soll später verwendet werden.
Für den aktuellen Bibliotheksbedarf wird das Ereignis "New tick" auf einem Symbol durch die Methode IsNewTick() definiert:
//+------------------------------------------------------------------+ //| Return the new tick flag | //+------------------------------------------------------------------+ bool CNewTickObj::IsNewTick(void) { //--- If failed to get the current prices to the tick structure, return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) return false; //--- If this is the first launch, copy data of the obtained tick to the previous tick data //--- reset the first launch flag and return 'false' if(this.m_first_start) { this.m_tick_prev=this.m_tick; this.m_first_start=false; return false; } //--- If the time of a new tick is not equal to the time of a tick during the previous check - //--- copy data of the obtained tick to the previous tick data and return 'true' if(this.m_tick.time_msc!=this.m_tick_prev.time_msc) { this.m_tick_prev=this.m_tick; return true; } //--- In all other cases, return 'false' return false; } //+------------------------------------------------------------------+
Die Klasse hat zwei definierte Konstruktoren:
- der Standardkonstruktor ohne Parameter wird verwendet, um das Objekt "New tick" innerhalb einer anderen Klasse zu definieren. In diesem Fall verwenden Sie die Klassenmethode SetSymbol(), um ein Symbol für das Klassenobjekt CNewTickObj zu setzen, für das die Ereignisse "New tick" definiert sind.
- der paramterische Konstruktor wird verwendet, um das Klassenobjekt über den Operator new zu erzeugen. In diesem Fall kann ein Symbol, für das das Objekt erzeugt wird, sofort bei der Erzeugung des Objekts angegeben werden.
//+------------------------------------------------------------------+ //| Parametric constructor CNewTickObj | //+------------------------------------------------------------------+ CNewTickObj::CNewTickObj(const string symbol) : m_symbol(symbol) { //--- Reset the structures of the new and previous ticks ::ZeroMemory(this.m_tick); ::ZeroMemory(this.m_tick_prev); //--- If managed to get the current prices to the tick structure, //--- copy data of the obtained tick to the previous tick data and reset the first launch flag if(::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_tick_prev=this.m_tick; this.m_first_start=false; } } //+------------------------------------------------------------------+
Dies ist die gesamte Klasse des neuen Tick-Objekts. Die Idee ist einfach: Man bringt die Preise in die Tickstruktur und vergleicht die Zeit des eingetroffenen Ticks mit der Zeit des vorherigen.
Wenn diese Zeiten nicht gleich sind, dann ist ein neuer Tick eingetroffen.
Ticks können in den EAs übersprungen werden, aber das ist hier nicht wichtig. Wir sind in der Lage, einen neuen Tick auf einem "nicht-originären" Symbol im Timer zu verfolgen, um die Daten nur dann zu aktualisieren, wenn ein neuer Tick eintrifft, und nicht ständig durch den Zeitgeber.
Wenn ein Indikator alle Ticks verfolgt, die in Stapeln eintreffen können, sollte die Aktualisierung der aktuellen Zeitreihendaten für ein Symbol, auf dem der Indikator gestartet wird, in OnCalculate() erfolgen. Die neuen Ticks für "nicht-originäre" Symbole werden im Timer verfolgt (neue Tick-Ereignisse für ein "nicht-originäres" Symbol können in OnOnCalculate() nicht empfangen werden), daher würde es ausreichen, nur die Zeitdifferenz zwischen den neuen und vorherigen Ticks für "nicht-originäre" Symbole zu verfolgen, um die Zeitreihendaten zeitlich zu aktualisieren.
Lassen Sie das Zeitreihenobjekt CSeries sein Ereignis "New bar" an das Steuerprogramm senden. Dies ermöglicht es uns, solche Ereignisse von jeder beliebigen Zeitreihe im Programm zu erhalten und darauf zu reagieren.
Fügen Sie am Ende der Enumerationsdatei Defines.mqh die neue Enumeration mit der Liste der möglichen Ereignisse der Zeitreihenobjekte hinzu:
//+------------------------------------------------------------------+ //| List of possible timeseries events | //+------------------------------------------------------------------+ enum ENUM_SERIES_EVENT { SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE, // no event SERIES_EVENTS_NEW_BAR, // "New bar" event }; #define SERIES_EVENTS_NEXT_CODE (SERIES_EVENTS_NEW_BAR+1) // Code of the next event after the "New bar" event //+------------------------------------------------------------------+
Hier haben wir bisher nur zwei Zustände der Zeitreihen-Ereignisse: "No event" und "New bar". Wir benötigen diese Enumerationskonstanten, um das Balken-Objekt nach bestimmten Eigenschaften in der Kollektionsliste der Balken (in der Zeitreihen CSeries) zu suchen.
Da die Zeitreihenobjekte im Bibliothekstimer aktualisiert werden, fügen wir die Parameter der Kollektion der Zeitreihen-Objekte für die Aktualisierung zusammen mit der ID der Zeitreihenkollektionsliste zur Datei Defines.mqh hinzu:
//--- Trading class timer parameters #define COLLECTION_REQ_PAUSE (300) // Trading class timer pause in milliseconds #define COLLECTION_REQ_COUNTER_STEP (16) // Trading class timer counter increment #define COLLECTION_REQ_COUNTER_ID (5) // Trading class timer counter ID //--- Parameters of the timeseries collection timer #define COLLECTION_TS_PAUSE (32) // Timeseries collection timer pause in milliseconds #define COLLECTION_TS_COUNTER_STEP (16) // Account timer counter increment #define COLLECTION_TS_COUNTER_ID (6) // Timeseries timer counter ID //--- Collection list IDs #define COLLECTION_HISTORY_ID (0x777A) // Historical collection list ID #define COLLECTION_MARKET_ID (0x777B) // Market collection list ID #define COLLECTION_EVENTS_ID (0x777C) // Event collection list ID #define COLLECTION_ACCOUNT_ID (0x777D) // Account collection list ID #define COLLECTION_SYMBOLS_ID (0x777E) // Symbol collection list ID #define COLLECTION_SERIES_ID (0x777F) // Timeseries collection list ID //--- Data parameters for file operations
Wir haben die Kollektionsparameter des Timers bei der Erstellung des CEngine Bibliotheks-Basisobjekts besprochen, während der Zweck der Kollektions-IDs beschrieben wurde, wenn die Bibliotheksstruktur neu geordnet wird.
Weisen Sie dem Balkenobjekt sofort die ID der Zeitreihenkollektion zu, da das Zeitreihen-Objekt eine Liste ist, die Zeiger auf Balkenobjekte enthält, die zu der Liste gehören.
Öffnen Sie \MQL5\Include\DoEasy\Objects\Series\Bar.mqh noch einmal und fügen Sie den Objekttyp zu beiden Konstruktoren hinzu:
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { this.m_type=COLLECTION_SERIES_ID; MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to write bar data to the MqlRates array by index or set the time to the time structure, //--- display an error message, create and fill the structure with zeros, and write it to the rates_array array if(::CopyRates(symbol,timeframe,index,1,rates_array)<1 || !::TimeToStruct(rates_array[0].time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; rates_array[0]=err; } //--- Set the bar properties this.SetProperties(rates_array[0]); } //+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates) { this.m_type=COLLECTION_SERIES_ID; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
Verbessern wir nun die Objektklasse der Zeitreihen CSeries, die sich in der Datei \MQL5\Include\DoEasy\Objects\Serie\Serie\Serie.mqh befindet.
Deklarieren Sie im 'public' Teil der Klasse die neue Methode zum Senden eines Ereignisses an die Kontrollprogramm auf dem Chart:
//--- (1) Create and (2) update the timeseries list int Create(const uint required=0); void Refresh(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); //--- Create and send the "New bar" event to the control program chart void SendEvent(void); //--- Return the timeseries name string Header(void); //--- Display (1) the timeseries description and (2) the brief timeseries description in the journal void Print(void); void PrintShort(void); //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); }; //+------------------------------------------------------------------+
Implementieren Sie am Ende der Klassen die deklarierte Methode:
//+------------------------------------------------------------------+ //| Create and send the "New bar" event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeries::SendEvent(void) { ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,this.Time(0),this.Timeframe(),this.Symbol()); } //+------------------------------------------------------------------+
Hier erzeugen wir und senden ein Ereignis an die Kontrollprogramm auf dem Chart. Das Ereignis besteht aus:
Chart ID des Ereignisempfängers,
Ereignis-ID (New bar),
sendet die neue Eröffnungszeit des Balkens als Ereignisparameter vom Tyo long,
sendet den Zeitrahmen des Charts, in dem das Ereignis aufgetreten ist, als Ereignisparameter vom Typ double und
sendet den Namen eines Symbols (in dessen Zeitreihe das Ereignis aufgetreten ist) als Parameter vom Typ string.
fügt die Prüfung des Flags, das die Verwendung der Zeitreihe im Programm anzeigt, der Synchronisationsmethode für Zeitreihendaten hinzu:
//+------------------------------------------------------------------+ //|Synchronize symbol and timeframe data with server data | //+------------------------------------------------------------------+ bool CSeries::SyncData(const uint required,const uint rates_total) { //--- If the timeseries is not used, notify of that and exit if(!this.m_available) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE)); return false; } //--- If managed to obtain the available number of bars in the timeseries
Mit anderen Worten, wenn das Flag der Zeitreihennutzung im Programm nicht gesetzt ist, besteht keine Notwendigkeit, es zu synchronisieren. Es kann auch eine Situation geben, in der wir die Zeitreihe benötigen, obwohl das Verwendungsflag nicht gesetzt ist. Daher wird die entsprechende Nachricht an das Journal gesendet.
Implementieren wir die gleiche Prüfung auf die Zeitreihenerstellungsmethode:
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeries::Create(const uint required=0) { //--- If the timeseries is not used, notify of that and return zero if(!this.m_available) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE)); return 0; } //--- If the required history depth is not set for the list yet,
Die Methode in der Klasse, die das Objekt bar durch den Zeitreihenindex zurückgibt, wurde überarbeitet. Zuvor sah die Methode wie folgt aus:
//+------------------------------------------------------------------+ //| Return the bar object by index in the timeseries | //+------------------------------------------------------------------+ CBar *CSeries::GetBarBySeriesIndex(const uint index) { CArrayObj *list=this.GetList(BAR_PROP_INDEX,index); return(list==NULL || list.Total()==0 ? NULL : list.At(0)); } //+------------------------------------------------------------------+
Mit anderen Worten: eine neue Liste mit der Kopie des benötigten Balkens wurde erstellt und diese Kopie wurde zurückgegeben. Dies ist ausreichend, wenn wir lediglich Daten eines angeforderten Balkens erhalten wollen, aber wenn wir die Eigenschaften des Balkens ändern müssen, dann funktioniert diese Methode nicht, da die Änderungen an den Eigenschaften der Kopie des Balkens und nicht an den Eigenschaften des Originalobjektes vorgenommen werden.
Da wir wollen, dass der aktuelle Balken in Echtzeit aktualisiert wird, wenn ein neuer Tick ankommt, habe ich die Methode so geändert, dass der Zeiger auf das ursprüngliche Balkenobjekt in der Kollektionsliste der Balken zurückgegeben wird und nicht die Kopie des Balkens aus der Liste:
//+------------------------------------------------------------------+ //| Return the bar object by index in the timeseries | //+------------------------------------------------------------------+ CBar *CSeries::GetBarBySeriesIndex(const uint index) { CBar *tmp=new CBar(this.m_symbol,this.m_timeframe,index); if(tmp==NULL) return NULL; this.m_list_series.Sort(SORT_BY_BAR_INDEX); int idx=this.m_list_series.Search(tmp); delete tmp; CBar *bar=this.m_list_series.At(idx); return(bar!=NULL ? bar : NULL); } //+------------------------------------------------------------------+
Hier erstellen wir das temporäre Balkenobjekt mit einem Symbol und Periode des aktuellen Zeitreihenobjektchart und dem Balkenindex, der an die Methode übergeben wird. Der Balkenindex in der Zeitreihe des Charts ist notwendig um das gleiche Objekt in der Zeitreihenliste zu suchen sortiert nach Balkenindizes. Bei der Suche nach einem Balken mit dem gleichen Zeitreihenindex holen wir seinen Index in der Liste (dieser Index wird verwendet, um den Zeiger auf das Balkenobjekt in der Liste zu erhalten) und geben den Zeiger auf das Objekt zurück.
Jetzt gibt die Methode den Zeiger auf das ursprüngliche Balkenobjekt in der Zeitreihenliste zurück. Er kann während der Echtzeit-Datenaktualisierung geändert werden.
Verbessern wir jetzt die Objektklasse der Zeitreihen CTimeSeries , um bei der Definition eines solchen Ereignisses neue Ticks zu verfolgen und Daten zu aktualisieren. Das Klassenobjekt ist eine Gruppe von Zeitreihen aller verwendeten Chartperioden eines einzelnen Symbols. Dies bedeutet, dass das Objekt der beste Ort für das Klassenobjekt "New tick" ist, da das Erhalten eines neuen Ticks durch das CTimeSeries Zeitreihen-Objektsymbol die Aktualisierung der Daten des Zeitreihen-Objekts CSeries aller zu dem Objekt gehörenden Perioden startet.
Einfügen der Objektklassendatei "New tick" in die Zeitreihen-Objektklassendatei. Im 'private' Teil der Klasse definieren wir das Objekt der Objektklasse "New tick".
Fügen Sie im 'public' Bereich der Klasse die Methode hinzu, die das neue Tick-Flag zurückgibt auf dem Symbol des aktuellen Zeitreihenobjekts:
//+------------------------------------------------------------------+ //| TimeSeries.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Series.mqh" #include "..\Ticks\NewTickObj.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeries : public CBaseObj { private: string m_symbol; // Timeseries symbol CNewTickObj m_new_tick; // "New tick" object CArrayObj m_list_series; // List of timeseries by timeframes datetime m_server_firstdate; // The very first date in history by a server symbol datetime m_terminal_firstdate; // The very first date in history by a symbol in the client terminal //--- Return (1) the timeframe index in the list and (2) the timeframe by the list index char IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1; } ENUM_TIMEFRAMES TimeframeByIndex(const uchar index) const { return TimeframeByEnumIndex(uchar(index+1)); } //--- Set the very first date in history by symbol on the server and in the client terminal void SetTerminalServerDate(void) { this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE); this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE); } public: //--- Return (1) oneself, (2) the full list of timeseries, (3) specified timeseries object and (4) timeseries object by index CTimeSeries *GetObject(void) { return &this; } CArrayObj *GetListSeries(void) { return &this.m_list_series; } CSeries *GetSeries(const ENUM_TIMEFRAMES timeframe) { return this.m_list_series.At(this.IndexTimeframe(timeframe)); } CSeries *GetSeriesByIndex(const uchar index) { return this.m_list_series.At(index); } //--- Set/return timeseries symbol void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } string Symbol(void) const { return this.m_symbol; } //--- Set the history depth (1) of a specified timeseries and (2) of all applied symbol timeseries bool SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SetRequiredAllUsedData(const uint required=0,const int rates_total=0); //--- Return the flag of data synchronization with the server data of the (1) specified timeseries, (2) all timeseries bool SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SyncAllData(const uint required=0,const int rates_total=0); //--- Return the very first date in history by symbol (1) on the server, (2) in the client terminal and (3) the new tick flag datetime ServerFirstDate(void) const { return this.m_server_firstdate; } datetime TerminalFirstDate(void) const { return this.m_terminal_firstdate; } bool IsNewTick(void) { return this.m_new_tick.IsNewTick(); } //--- Create (1) the specified timeseries list and (2) all timeseries lists bool Create(const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateAll(const uint required=0); //--- Update (1) the specified timeseries list and (2) all timeseries lists void Refresh(const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); void RefreshAll(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); //--- Compare CTimeSeries objects (by symbol) virtual int Compare(const CObject *node,const int mode=0) const; //--- Display (1) description and (2) short symbol timeseries description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructors CTimeSeries(void){;} CTimeSeries(const string symbol); }; //+------------------------------------------------------------------+
Die Methode IsNewTick() gibt das Ergebnis der Anforderung von Daten über den neuen Tick aus dem Objekt "New tick" m_new_tickzurück.
Damit das Objekt der Klasse "New tick" das Symbol kennt, dessen Daten zurückgegeben werden sollen, sollten wir das Symbol für das Objekt der Klasse "New tick" im Klassenkonstruktor setzen und sofort Daten zum Lesen der aktuellen Tick-Preise aktualisieren:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol) { this.m_list_series.Clear(); this.m_list_series.Sort(); for(int i=0;i<21;i++) { ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i); CSeries *series_obj=new CSeries(this.m_symbol,timeframe); this.m_list_series.Add(series_obj); } this.SetTerminalServerDate(); this.m_new_tick.SetSymbol(this.m_symbol); this.m_new_tick.Refresh(); } //+------------------------------------------------------------------+
Wir werden jetzt das Zeitreihenverwendungsflags in den Methoden prüfen, die das Datensynchronisationsflag zurückgeben. Wenn das Flag nicht markiert ist, wird die Zeitreihe im Programm nicht verwendet und sollte nicht behandelt werden:
//+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL)); return false; } CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe)); return false; } if(!series_obj.IsAvailable()) return false; return series_obj.SyncData(required,rates_total); } //+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| of all timeseries with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncAllData(const uint required=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL)); return false; } bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || !series_obj.IsAvailable()) continue; res &=series_obj.SyncData(required,rates_total); } return res; } //+------------------------------------------------------------------+
Erzwingen wir in den Methoden zur Erstellung von Zeitreihen das Setzen der Flag für die Zeitreihenverwendung:
//+------------------------------------------------------------------+ //| Create a specified timeseries list | //+------------------------------------------------------------------+ bool CTimeSeries::Create(const ENUM_TIMEFRAMES timeframe,const uint required=0) { CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe)); return false; } if(series_obj.RequiredUsedData()==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return false; } series_obj.SetAvailable(true); return(series_obj.Create(required)>0); } //+------------------------------------------------------------------+ //| Create all timeseries lists | //+------------------------------------------------------------------+ bool CTimeSeries::CreateAll(const uint required=0) { bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || series_obj.RequiredUsedData()==0) continue; series_obj.SetAvailable(true); res &=(series_obj.Create(required)>0); } return res; } //+------------------------------------------------------------------+
In den Methoden zur Aktualisierung der Zeitreihe (für den Fall, dass das Ereignis "New bar" darin erkannt wird), Hinzufügen einer Nachricht über das Ereignis an das Kontrollprogramm auf dem Chart unter Verwendung der Methode SendEvent() des oben betrachteten Zeitreihenobjekts CSeries:
//+------------------------------------------------------------------+ //| Update a specified timeseries list | //+------------------------------------------------------------------+ void CTimeSeries::Refresh(const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL || series_obj.DataTotal()==0) return; series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread); if(series_obj.IsNewBar(time)) { series_obj.SendEvent(); this.SetTerminalServerDate(); } } //+------------------------------------------------------------------+ //| Update all timeseries lists | //+------------------------------------------------------------------+ void CTimeSeries::RefreshAll(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { bool upd=false; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || series_obj.DataTotal()==0) continue; series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread); if(series_obj.IsNewBar(time)) { series_obj.SendEvent(); upd &=true; } } if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
Verbessern wir die Zeitreihensammlungsklasse CTimeSeriesCollection in \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
Wir setzen den Typ der Kollektionsliste der Zeitreihen auf den Klassentyp CListObj.
Dazu müssen wir die Klassendatei CListObj einbinden und den Typ der Kollektionsliste von CArrayObj in CListObj ändern:
//+------------------------------------------------------------------+ //| TimeSeriesCollection.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Objects\Series\TimeSeries.mqh" #include "..\Objects\Symbols\Symbol.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CObject { private: CListObj m_list; // List of applied symbol timeseries //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public:
Wir deklarieren im 'public' Teil der Klasse die Methode zum Zurückgeben des angegebenen Zeitreihenbalkens durch den Zeitreihenindex des Charts, die Methode zum Zurückgeben des Flags zum Öffnen eines neuen Balkens einer angegebenen Zeitreihe und die Methode zum Aktualisieren von Zeitreihen, die nicht zum aktuellen Symbol gehören:
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries of the specified symbol, //--- (2) the specified timeseries of all symbols, (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SyncData(const string symbol,const uint required=0,const int rates_total=0); bool SyncData(const uint required=0,const int rates_total=0); //--- Return the bar of the specified timeseries of the specified symbol of the specified position CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true); //--- 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); //--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols void RefreshOther(void); //--- Display (1) the complete and (2) short collection description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructor CTimeSeriesCollection(); }; //+------------------------------------------------------------------+
Legen Sie im Klassenkonstruktor die ID der Kollektion der Zeitreihen für die Liste der Zeitreihenobjekte fest:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeriesCollection::CTimeSeriesCollection() { this.m_list.Clear(); this.m_list.Sort(); this.m_list.Type(COLLECTION_SERIES_ID); } //+------------------------------------------------------------------+
Implementierung der Methoden zur Rückgabe des Balkenobjekts durch den Zeitreihenindex und des neuen Balkenereignisses aus der angegebenen Zeitreihenliste:
//+-----------------------------------------------------------------------+ //| Return the bar of the specified timeseries | //| of the specified symbol of the specified position | //| from_series=true - by the timeseries index, false - by the list index | //+-----------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { //--- Get the timeseries object index in the timeseries collection list by a symbol name int idx=this.IndexTimeSeries(symbol); if(idx==WRONG_VALUE) return NULL; //--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index CTimeSeries *timeseries=this.m_list.At(idx); if(timeseries==NULL) return NULL; //--- Get the specified timeseries from the symbol timeseries object by the specified timeframe CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return NULL; //--- Depending on the from_series flag, return the pointer to the bar //--- either by the chart timeseries index or by the bar index in the timeseries list return(from_series ? series.GetBarBySeriesIndex(index) : series.GetBarByListIndex(index)); } //+------------------------------------------------------------------+ //| Return new bar opening flag | //| for a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { //--- Get the timeseries object index in the timeseries collection list by a symbol name int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; //--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; //--- Get the specified timeseries from the symbol timeseries object by the specified timeframe CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return false; //--- Return the result of checking the new bar of the specified timeseries return series.IsNewBar(time); } //+------------------------------------------------------------------+
Implementierung der Methode zur Aktualisierung aller Zeitreihen mit Ausnahme der aktuellen Symbolzeitreihen:
//+------------------------------------------------------------------+ //| Update all timeseries except the current symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::RefreshOther(void) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(timeseries.Symbol()==::Symbol() || !timeseries.IsNewTick()) continue; timeseries.RefreshAll(); } } //+------------------------------------------------------------------+
In der Schleife durch die Liste aller Zeitreihenobjekte, holen wir uns das nächste Zeitreihenobjekt. Wenn das Objektsymbol gleich einem Symbol eines Charts ist, auf dem das Programm auf gestartet wird, wird ein solches Zeitreihenobjekt übersprungen.
Diese Methode sowie die unten beschriebenen Methoden zur Aktualisierung von Zeitreihen erlauben die Prüfung auf das neue Tick-Flag. Wenn es kein neues Tick-Flag gibt, wird die Zeitreihe übersprungen und ihre Daten werden nicht aktualisiert:
//+------------------------------------------------------------------+ //| Update the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; if(!timeseries.IsNewTick()) return; timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread); } //+------------------------------------------------------------------+ //| Update the specified timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(!timeseries.IsNewTick()) continue; timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+ //| Update all timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; if(!timeseries.IsNewTick()) return; timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread); } //+------------------------------------------------------------------+ //| Update all timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(!timeseries.IsNewTick()) continue; timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+
Der letzte Schritt besteht darin, die notwendigen Verbesserungen an der Datei der Hauptobjektklasse der CEngine-Bibliothek vorzunehmen.
Öffnen Sie die Klassendatei in \MQL5\Include\DoEasy\Engine.mqh.
In der 'private' Teil der Klasse deklarieren wir die Variable zur Speicherung des Programmtyps basierend auf der Bibliothek:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CTimeSeriesCollection m_series; // Timeseries collection CResourceCollection m_resource; // Resource list CTradingControl m_trading; // Trading management object CArrayObj m_list_counters; // List of timer counters int m_global_error; // Global error code bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag bool m_is_account_event; // Account change event flag bool m_is_symbol_event; // Symbol change event flag ENUM_TRADE_EVENT m_last_trade_event; // Last account trading event int m_last_account_event; // Last event in the account properties int m_last_symbol_event; // Last event in the symbol properties ENUM_PROGRAM_TYPE m_program; // Program type
Im 'public' Teil der Klasse deklarieren wir die Methode zur Behandlung der EA-Ereignisse von NewTick:
//--- (1) NewTick event timer and (2) handler void OnTimer(void); void OnTick(void);
Im gleichen 'public' Teil deklarieren wir die Methode, die das Balkenobjekt der angegebenen Zeitreihe des angegebenen Symbols durch den Zeitreihenindex des Charts zurückgibt, und die Methode, die das Flag zum Öffnen eines neuen Balkens der angegebenen Zeitreihe des angegebenen Symbols zurückgibt:
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(symbol,timeframe,required); } bool SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(timeframe,required); } bool SeriesCreate(const string symbol,const uint required=0) { return this.m_series.CreateSeries(symbol,required); } bool SeriesCreate(const uint required=0) { return this.m_series.CreateSeries(required); } //--- Return the bar of the specified timeseries of the specified symbol of the specified position CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { return this.m_series.GetBar(symbol,timeframe,index,from_series); } //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { return this.m_series.IsNewBar(symbol,timeframe,time); } //--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
Im gleichen Klassenabschnitt deklarieren wir die Methoden, die Standardbalkeneigenschaften für ein bestimmtes Symbol, eine Zeitreihe und seine Position in der Zeitreihe (Balkenindex) des Charts zurückgeben:
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the specified bar of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); //--- Set the following for the trading classes: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts
Wir legen im Klassenkonstruktor den Typ des laufenden Programms fest und erzeugen den Zähler des Timers für die Kollektion der Zeitreihen:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE); this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE); this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1); this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2); this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE); this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } #endif //--- } //+------------------------------------------------------------------+
Fügen wir im OnTimer() der Bibliothek die Arbeit mit dem Timer für die Zeitreihenkollektion hinzu (überflüssiger Code entfernt):
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer of the collections of historical orders and deals, as well as of market orders and positions //... //--- Account collection timer //... //--- Timer 1 of the symbol collection (updating symbol quote data in the collection) //... //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) //... //--- Trading class timer //... //--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If the pause is over, work with the timeseries list (except for the current symbol timeseries) if(counter.IsTimeDone()) this.m_series.RefreshOther(); } //--- In case of the tester, work with the timeseries list by tick (except for the current symbol timeseries) else this.m_series.RefreshOther(); } } } //+------------------------------------------------------------------+
Bei der Arbeit mit den Timer für die Kollektion und mit dem Timer selbst wurde bei der Erstellung des Bibliothekshauptobjekts CEngine berücksichtigt. Alles andere wird in den Kommentaren des Codes beschrieben.
Beachten Sie, dass der Timer nur die Zeitreihen verarbeitet, deren Symbol nicht mit dem Symbol eines Charts übereinstimmt, auf dem das Programm gestartet wird.
In der Zeitschaltuhr aktualisieren wir die Zeitreihen bei der Registrierung der Ereignisse von "New tick" für "nicht-originäre" Symbole. Daher sind dies die Ereignisse, die wir im Timer erkennen.
Die Methode OnTick(), die aus der EA-Funktion OnTick() gestartet wird, wird verwendet, um die aktuelle Symbolzeitreihe zu aktualisieren:
//+------------------------------------------------------------------+ //| NewTick event handler | //+------------------------------------------------------------------+ void CEngine::OnTick(void) { //--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) return; //--- Update the current symbol timeseries this.SeriesRefresh(NULL,PERIOD_CURRENT); } //+------------------------------------------------------------------+
Implementieren der Methoden zum Empfang der Haupteigenschaften des angegebenen Balkens der angegebenen Zeitreihe:
//+------------------------------------------------------------------+ //| Return the specified bar's Open | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Open() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's High | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.High() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Low | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Low() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Close | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Close() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's TickVolume | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return the specified bar's RealVolume | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return the specified bar's Spread | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Spread() : INT_MIN); } //+------------------------------------------------------------------+
Hier ist alles einfach: Wir erhalten das Balkenobjekt durch Zeitreihensymbol und Zeitrahmen aus dem spezifizierten Index der Chartzeitreihe (0 - aktueller Balken) und Rückgabe der entsprechenden Balkeneigenschaft.
Dies alles sind die Verbesserungen, die heute erforderlich sind, um eine automatische Aktualisierung der im Programm verwendeten Zeitreihen-Preisdaten zu erstellen, Ereignisse an das Kontrollprogramm auf dem Chart zu senden und Daten aus der erstellten Zeitreihe im Programm zu empfangen.
Tests
Lassen Sie uns den Test folgendermaßen durchführen:
Wir erstellen drei Zeitreihen für die aktuellen Zeitrahmen von drei Symbolen, das Balkenobjekt Null (Klasse CBar) Zeitreihenkollektion (CTimeSeriesCollection) abrufen und die Balkendaten als Kommentar im Chart mit den Methoden anzeigen, die den Kurznamen des Balkenobjekts + Beschreibung der Parameter des Balkenobjekts zurückgeben. Die zweite Kommentarzeile soll die Balkendaten von Null in einem ähnlichen Format enthalten. In diesem Fall werden die Daten jedoch mit den Methoden des Hauptobjekts der CEngine-Bibliothek erzeugt, die die Daten des angegebenen Balkens des angegebenen Symbols des angegebenen Zeitrahmens zurückgeben.
Die Daten sind im Tester und auf dem Chart, auf dem der EA gestartet wird, in Echtzeit zu aktualisieren.
Wir werden auch die Behandlung des Empfangs von Ereignissen aus den Objekten der Klasse CSeries implementieren, die das Ereignis "New bar" an das Kontrollprogramm auf dem Chart senden, und den Empfang dieser Ereignisse im Programm beobachten, das auf einer Chartsymbol gestartet wird.
Um den Test durchzuführen, werden wir den EA aus dem vorherigen Artikel verwenden und ihn in \MQL5\Experts\TestDoEasy\Part38\ unter dem Namen TestDoEasyPart38.mq5 speichern.
Prüfen wir OnTick() des EAs wie folgt:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); // Working in the timer PressButtonsControl(); // Button pressing control EventsHandling(); // Working with events } //--- Handle the NewTick event in the library engine.OnTick(); //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } //--- Bet the zero bar of the current timeseries CBar *bar=engine.SeriesGetBar(NULL,PERIOD_CURRENT,0); if(bar==NULL) return; //--- Create a string of parameters of the current bar similar to the one //--- displayed by the bar object description: //--- bar.Header()+": "+bar.ParameterDescription() string parameters= (TextByLanguage("Бар \"","Bar \"")+Symbol()+"\" "+TimeframeDescription((ENUM_TIMEFRAMES)Period())+"[0]: "+TimeToString(bar.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+ ", O: "+DoubleToString(engine.SeriesOpen(NULL,PERIOD_CURRENT,0),Digits())+ ", H: "+DoubleToString(engine.SeriesHigh(NULL,PERIOD_CURRENT,0),Digits())+ ", L: "+DoubleToString(engine.SeriesLow(NULL,PERIOD_CURRENT,0),Digits())+ ", C: "+DoubleToString(engine.SeriesClose(NULL,PERIOD_CURRENT,0),Digits())+ ", V: "+(string)engine.SeriesTickVolume(NULL,PERIOD_CURRENT,0)+ ", Real: "+(string)engine.SeriesRealVolume(NULL,PERIOD_CURRENT,0)+ ", Spread: "+(string)engine.SeriesSpread(NULL,PERIOD_CURRENT,0) ); //--- Display the data received from the bar object in the first line of the chart comment, //--- while the second line contains the methods of receiving timeseries price data Comment(bar.Header(),": ",bar.ParameterDescription(),"\n",parameters); } //+------------------------------------------------------------------+
Hier ist alles einfach: der Codeblock ist eine Standardvorlage bei der Arbeit mit der DoEasy-Bibliothek. Die aktuelle Implementierung funktioniert den Aufruf des NewTick-Ereignishandlers , der von der Bibliothek bei jedem Tick behandelt wird (derzeit führt sie die Aktualisierung der erzeugten Zeitreihen durch). Alle fehlenden Zeitreihen (die mit den Create()-Methoden deklariert, aber nicht erzeugt wurden) werden übersprungen (nicht von der Bibliothek aktualisiert). In Zukunft wird der Aufruf dieser Methode aus OnTick() der EAs erforderlich sein, um die aktuellen Zeitreihendaten zu aktualisieren.
Als Nächstes empfangen wir das Balkenobjekt vom aktuellen Symbol und der Periodenzeitreihe, erzeugen den String mit der Beschreibung der erhaltenen Taktdaten und zeigen zwei Zeilen im Kommentar an:
die erste Zeile wird mit den Methoden des Balkenobjekts angezeigt,
die zweite besteht aus Daten, die mit den Methoden des Hauptobjekts der Bibliothek erhalten wurden, die die angeforderten Balken-Daten zurückgeben.
Die Bibliotheksinitialisierungsfunktion OnInitDoEasy() verfügt über den leicht veränderten Codeblock zur Erstellung der Zeitreihe aller verwendeten Symbole:
//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function #ifdef __MQL5__ if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT) ArrayPrint(array_used_periods); #endif //--- Create timeseries of all used symbols CArrayObj *list_timeseries=engine.GetListTimeSeries(); if(list_timeseries!=NULL) { int total=list_timeseries.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=list_timeseries.At(i); int total_periods=ArraySize(array_used_periods); for(int j=0;j<total_periods;j++) { ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]); timeseries.SyncData(timeframe); timeseries.Create(timeframe); } } } //--- Check created timeseries - display descriptions of all created timeseries in the journal //--- (true - only created ones, false - created and declared ones) engine.GetTimeSeriesCollection().PrintShort(true); // Short descriptions //engine.GetTimeSeriesCollection().Print(true); // Full descriptions
Hier erhalten wir die Liste aller Zeitreihen und, in der Schleife durch die Zeitreihenliste, das nächste Zeitreihenobjekt durch den Schleifenindex. Dann in der Schleife durch die Anzahl der verwendeten Zeitrahmen, erzeugen wir die benötigte Zeitreihenliste nach dem Synchronisieren der Zeitreihen- und der historischen Daten.
In der Funktion OnDoEasyEvent(), die die Bibliotheksereignisse behandelt, fügen wir den Codeblock für die Behandlung von Zeitreihen-Ereignissen hinzu (der redundante Code wurde entfernt):
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events //... //--- Handling account events //... //--- Handling market watch window events //... //--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } } //--- Handling trading events //... } //+------------------------------------------------------------------+
Hier wenn die erhaltene Ereignis-ID innerhalb der Zeitreihen-Ereignis-IDs liegt und wenn es sich um das Ereignis "New bar" handelt, zeigen wir die Nachricht über das Ereignis im Terminal-Journal an.
Kompilieren Sie den EA und stellen Sie seine Parameter wie folgt ein:
- setzen Sie Mode of used symbols list für die Verwendung einer bestimmten Symbolliste,
- lassen Sie in List of used symbols (comma - separator) nur drei Symbole stehen, eines davon ist EURUSD und
- wählen Sie z.B. aus der Liste Mode of used timeframes list, dass Sie nur mit dem aktuellen Zeitrahmen arbeiten möchten:
Starten Sie den EA auf dem Chart. Nach einer Weile zeigt das Journal die Ereignismeldungen "New bar" zu den für das aktuelle Chartsymbol verwendeten Symbolen an:
New bar on EURUSD M5: 2020.03.11 12:55 New bar on EURAUD M5: 2020.03.11 12:55 New bar on AUDUSD M5: 2020.03.11 12:55 New bar on EURUSD M5: 2020.03.11 13:00 New bar on AUDUSD M5: 2020.03.11 13:00 New bar on EURAUD M5: 2020.03.11 13:00
Starten Sie den EA im visuellen Tester-Modus auf dem Chart eines der in den Einstellungen ausgewählten Symbole, z.B. auf EURUSD, und sehen Sie, wie sich die Nullbalken-Daten im Kommentar des Charts ändern:
Wie wir sehen können, enthalten beide Zeilen Daten, die auf unterschiedliche Weise erhalten wurden, identische Werte der empfangenen Nullbalkeneigenschaften und werden bei jedem Tick in Echtzeit aktualisiert.
Was kommt als Nächstes?
Im nächsten Artikel werden wir einige Unzulänglichkeiten der aktuellen Bibliotheksversion beheben, die nach Fertigstellung des aktuellen Artikels festgestellt wurden, und die Entwicklung des Konzepts der Arbeit mit Zeitreihen fortsetzen, indem wir die Bibliothek auf die Arbeit als Teil von Indikatoren vorbereiten.
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.
Schreiben Sie Ihre Fragen und Vorschläge in den Kommentaren.
Frühere Artikel dieser Serie:
Zeitreihen in der Bibliothek DoEasy (Teil 35): das Bar-Objekt und die Liste der Zeitreihen eines Symbols
Zeitreihen in der Bibliothek DoEasy (Teil 36): Objekt der Zeitreihe für alle verwendeten Symbolperioden
Zeitreihen in der Bibliothek DoEasy (Teil 37): Kollektion von Zeitreihen - Datenbank der Zeitreihen nach Symbolen und Zeitrahmen