Zeitreihen in der Bibliothek DoEasy (Teil 35): das Bar-Objekt und die Liste der Zeitreihen eines Symbols
Artyom Trishkin | 1 Juni, 2020
Inhalt
- Konzept
- Das Bar-Objekt
- Ein neues Bar-Objekt
- Die Liste der Bar-Objekte, Suchen und Sortieren
- Tests
- Was kommt als Nächstes?
Dieser Artikel beginnt den neuen Abschnitt in der Beschreibung der Bibliothek zur bequemen Entwicklung von Programmen für MetaTrader 5 und 4 Terminals. Die erste Serie (34 Artikel) war dem Konzept der Bibliotheksobjekte und ihrer Verbindungen gewidmet. Das Konzept wurde verwendet, um die Funktionen für die Arbeit mit einem Konto zu entwickeln — seinen aktuellen Status und seine Historie.
Gegenwärtig verfügt die Bibliothek über die folgenden Funktionen:
- Suchen, Sortieren und Vergleichen der Daten von jeder Order oder Position,
- schneller und bequemer Zugang zu allen Eigenschaften von Order und Position,
- Verfolgen jedes Kontoereignisses,
- Beschaffen und Vergleichen von Konto- und Symboldaten,
- Reagieren auf Eigenschaftsänderungen aller in Datenbanken (Sammlungen) vorhandenen Objekte und Senden von Benachrichtigungen über aufgetretene Ereignisse an das Programm.
Wir können der Bibliothek auch mitteilen, auf welche Ereignisse sie reagieren und Benachrichtigungen über die überwachten Ereignisse senden soll.
Außerdem haben wir die Möglichkeit implementiert, mit den Handelsfunktionen des Terminals zu arbeiten. Wir haben auch neue Arten von Handelsanfragen entwickelt — schwebende Handelsanfragen, die es uns ermöglichen, die Verhaltenslogik des Programms unter verschiedenen Handelsbedingungen zu erstellen, sowie den vollständigen Satz von Funktionen für die Erstellung neuer Arten von schwebenden Aufträgen bereitzustellen.
All dies und noch viel mehr wurde in den vorangegangenen Artikeln der Bibliotheksentwicklungsbeschreibungen erstellt und beschrieben.
Die zweite Reihe der Bibliotheksbeschreibung berücksichtigt viele Aspekte der Arbeit mit Preisdaten, Symboldiagrammen, Markttiefen, Indikatoren usw. Generell soll die neue Artikelserie der Entwicklung der Bibliotheksfunktionalität für den schnellen Zugriff, die Suche, den Vergleich und die Sortierung beliebiger Preisdatenfelder sowie deren Speicherobjekte und Quellen gewidmet werden.
Konzept
Das Konzept der Anordnung der Bibliotheksobjekte, ihre Aufbewahrung und die Arbeit mit ihnen wurden in den ersten Artikeln der vorherigen Serie ausführlich beschrieben. Im aktuellen Artikel werde ich kurz die Grundprinzipien des Ordnens und Aufbewahrens der Bibliotheksobjekte wiederholen.
Jeder Satz gleichartiger Daten kann als eine Liste von Objekten mit identischen Eigenschaften dargestellt werden, die diesem Datentyp eigen sind. Jedes Objekt aus der Liste gleichartiger Objekte weist einen ähnlichen Satz von Eigenschaften auf, obwohl jedes Objekt der gleichen sortierten Liste unterschiedliche Werte hat.
Jede Liste, die einen Satz gleichartiger Objekte speichert, kann nach jeder der Eigenschaften sortiert werden, die die Listenobjekte haben.Die Objekteigenschaften haben drei Haupttypen — ganzzahlige, reelle und Zeichenketten.
Die Sortierung solcher Objekte in sortierten Listen ermöglicht es uns, jedes der Objekte schnell zu finden, alle Daten daraus zu erhalten und sie in benutzerdefinierten Programmen zu verwenden.Fast alle Bibliotheksobjekte sind in der Lage, ihre eigenen Eigenschaften unabhängig zu verfolgen und ihre Werte um einen bestimmten Betrag zu ändern, der für jedes Objekt, an dem wir interessiert sind, eingestellt werden kann. Wenn Objekteigenschaften um einen bestimmten Wert geändert werden, sendet das verfolgte Objekt die Meldung an das Steuerprogramm, dass der für die Verfolgung eingestellte Schwellenwert erreicht wurde.
Die Basis aller Bibliotheksobjekte ist ein Basis-Standard-Bibliotheksobjekt, das mit der Handelsplattform geliefert wird, während die Objektliste (Objektkollektion) ein dynamisches Array von Zeigern auf CObject-Klasseninstanzen und ihre abgeleiteten Klassen der Standard-Bibliothek ist.
Im aktuellen Artikel werde ich das Bar-Objekt erstellen, das die gesamten Daten eines einzelnen Balkens in einem Symboldiagramm sowie die Liste aller Bar-Objekte für ein einzelnes Symbol und einen einzelnen Zeitrahmen speichern soll.
Jede Bar eines Symboldiagramms hat einen bestimmten Satz von Parametern, die in der Struktur MqlRates beschrieben sind:
- Perioden-Startzeit
- Eröffnungspreis
- höchster Preis im Zeitraum
- niedrigster Preis im Zeitraum
- Schlusskurs
- Tick-Volumen
- Spreizung (Spread)
- Börsen-Volumen
Abgesehen von den Eigenschaften des Bar-Objekts können wir die zusätzlichen Eigenschaften direkt beim Erstellen eines Objekts festlegen:
- Das Jahr, zu dem die Bar gehört.
- Der Monat, zu dem der Balken gehört.
- Der Wochentag der Bar.
- Die Seriennummer der Bar im Jahr.
- Der Tag des Monats (Nummer).
- Die Stunde der Bar.
- Die Minute der Bar.
- Balkenindex in einer Zeitreihe des Symbols.
- Bar (von Hoch bis Tief).
- Kerzenkörper Oben (Max(Open,Close)).
- Kerzenkörper Unten (Min(Open,Close)).
- Kerzenkörpergröße (Oben - Unten).
- Kerzendocht oben (vom Hoch bis Oben des Kerzenkörpers).
- Kerzendocht unten (von Unten des Kerzenkörpers bis zum Tief).
Alle diese Eigenschaften ermöglichen es uns, nach beliebigen Kombinationen von Bars und Kerzen (in der Barliste gespeichert) innerhalb des Bereichs zu suchen. Der gewünschte Bereich der gespeicherten Daten kann für die Liste der Bar-Objekte eingestellt werden. Standardmäßig speichern wir tausend Bars in der Liste (oder die gesamte verfügbare Historie der Bars des Symbols, wenn es sich um weniger als tausend Bars oder einen anderen festgelegten Bereich handelt).
Das Bar-Objekt
Erstellen wir die Aufzählungen aller Eigenschaften der Bar-Objekte. Dazu öffnen wir die Datei Defines.mqh, die in \MQL5\Include\DoEasy\Defines.mqh gespeichert ist und fügen am Ende der Datei die Enumeration der Eigenschaften des Bar-Typs, ganzzahlig, reell und Zeichen der Bar-Objekte, sowie die Methoden zum Sortieren der Liste der Bar-Objekte hinzu:
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Info for working with serial data | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Bar type (candle body) | //+------------------------------------------------------------------+ enum ENUM_BAR_BODY_TYPE { BAR_BODY_TYPE_BULLISH, // Bullish bar BAR_BODY_TYPE_BEARISH, // Bearish bar BAR_BODY_TYPE_NULL, // Zero bar BAR_BODY_TYPE_CANDLE_ZERO_BODY, // Candle with a zero body }; //+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_INDEX = 0, // Bar index in timeseries BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration) BAR_PROP_PERIOD, // Bar period (timeframe) BAR_PROP_SPREAD, // Bar spread BAR_PROP_VOLUME_TICK, // Bar tick volume BAR_PROP_VOLUME_REAL, // Bar exchange volume BAR_PROP_TIME, // Bar period start time BAR_PROP_TIME_DAY_OF_YEAR, // Bar day serial number in a year BAR_PROP_TIME_YEAR, // A year the bar belongs to BAR_PROP_TIME_MONTH, // A month the bar belongs to BAR_PROP_TIME_DAY_OF_WEEK, // Bar week day BAR_PROP_TIME_DAY, // Bar day of month (number) BAR_PROP_TIME_HOUR, // Bar hour BAR_PROP_TIME_MINUTE, // Bar minute }; #define BAR_PROP_INTEGER_TOTAL (14) // Total number of integer bar properties #define BAR_PROP_INTEGER_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+ //| Real bar properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_DOUBLE { //--- bar data BAR_PROP_OPEN = BAR_PROP_INTEGER_TOTAL, // Bar open price BAR_PROP_HIGH, // Highest price for the bar period BAR_PROP_LOW, // Lowest price for the bar period BAR_PROP_CLOSE, // Bar close price //--- candle data BAR_PROP_CANDLE_SIZE, // Candle size BAR_PROP_CANDLE_SIZE_BODY, // Candle body size BAR_PROP_CANDLE_BODY_TOP, // Candle body top BAR_PROP_CANDLE_BODY_BOTTOM, // Candle body bottom BAR_PROP_CANDLE_SIZE_SHADOW_UP, // Candle upper wick size BAR_PROP_CANDLE_SIZE_SHADOW_DOWN, // Candle lower wick size }; #define BAR_PROP_DOUBLE_TOTAL (10) // Total number of real bar properties #define BAR_PROP_DOUBLE_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+ //| Bar string properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_STRING { BAR_PROP_SYMBOL = (BAR_PROP_INTEGER_TOTAL+BAR_PROP_DOUBLE_TOTAL), // Bar symbol }; #define BAR_PROP_STRING_TOTAL (1) // Total number of string bar properties //+------------------------------------------------------------------+ //| Possible bar sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BAR_DBL_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP) #define FIRST_BAR_STR_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP) enum ENUM_SORT_BAR_MODE { //--- Sort by integer properties SORT_BY_BAR_INDEX = 0, // Sort by bar index SORT_BY_BAR_TYPE, // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration) SORT_BY_BAR_PERIOD, // Sort by bar period (timeframe) SORT_BY_BAR_SPREAD, // Sort by bar spread SORT_BY_BAR_VOLUME_TICK, // Sort by bar tick volume SORT_BY_BAR_VOLUME_REAL, // Sort by bar exchange volume SORT_BY_BAR_TIME, // Sort by bar period start time SORT_BY_BAR_TIME_DAY_OF_YEAR, // Sort by bar day number in a year SORT_BY_BAR_TIME_YEAR, // Sort by a year the bar belongs to SORT_BY_BAR_TIME_MONTH, // Sort by a month the bar belongs to SORT_BY_BAR_TIME_DAY_OF_WEEK, // Sort by a bar week day SORT_BY_BAR_TIME_DAY, // Sort by a bar day SORT_BY_BAR_TIME_HOUR, // Sort by a bar hour SORT_BY_BAR_TIME_MINUTE, // Sort by a bar minute //--- Sort by real properties SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP, // Sort by bar open price SORT_BY_BAR_HIGH, // Sort by the highest price for the bar period SORT_BY_BAR_LOW, // Sort by the lowest price for the bar period SORT_BY_BAR_CLOSE, // Sort by a bar close price SORT_BY_BAR_CANDLE_SIZE, // Sort by a candle price SORT_BY_BAR_CANDLE_SIZE_BODY, // Sort by a candle body size SORT_BY_BAR_CANDLE_BODY_TOP, // Sort by a candle body top SORT_BY_BAR_CANDLE_BODY_BOTTOM, // Sort by a candle body bottom SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP, // Sort by candle upper wick size SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN, // Sort by candle lower wick size //--- Sort by string properties SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP, // Sort by a bar symbol }; //+------------------------------------------------------------------+
Der Typ einer Bar kann auf-, abwärts oder Null sein. Ein separater Eigenschaftstyp der Bars wird verwendet, um einen Kerzenkörpertyp einzustellen, da die Kerzenkörpergröße einer der Parameter ist, der zur Definition verschiedener Kerzenformationen verwendet wird. Daher sollte eine Kerzenkörpergröße von Null nicht als äquivalent zum Null-Bar betrachtet werden. Eine Null-Bar hat nur einen Preis für alle seine vier OHLC-Preise, während die Nullkerzenkörper-Kerze Dochte haben kann, während die Lage und die Dochtgröße bei der Definition von Kerzenformationen berücksichtigt werden.
Um Beschreibungen der Bareigenschaften und einige andere Bibliotheksnachrichten anzuzeigen, benötigen wir neue Textnachrichten.
Die Datei Datas.mqh befindet sich in \MQL5\Include\DoEasy\Datas.mqh und erhält die Indizes der neuer Nachrichten:
MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE, // Return code out of range of error codes MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ, // Failed to create the \"Pause\" object MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ, // Failed to create the \"Bar\" object MSG_LIB_SYS_FAILED_SYNC_DATA, // Failed to synchronize data with the server
...
MSG_LIB_TEXT_TIME_UNTIL_THE_END_DAY, // Order lifetime till the end of the current day to be used MSG_LIB_TEXT_JANUARY, // January MSG_LIB_TEXT_FEBRUARY, // February MSG_LIB_TEXT_MARCH, // March MSG_LIB_TEXT_APRIL, // April MSG_LIB_TEXT_MAY, // May MSG_LIB_TEXT_JUNE, // June MSG_LIB_TEXT_JULY, // July MSG_LIB_TEXT_AUGUST, // August MSG_LIB_TEXT_SEPTEMBER, // September MSG_LIB_TEXT_OCTOBER, // October MSG_LIB_TEXT_NOVEMBER, // November MSG_LIB_TEXT_DECEMBER, // December MSG_LIB_TEXT_SUNDAY, // Sunday
...
MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS, // Added pending request activation conditions //--- CBar MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA, // Failed to receive bar data MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA, // Failed to receive timeseries data MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST, // Could not add bar object to the list MSG_LIB_TEXT_BAR, // Bar MSG_LIB_TEXT_BAR_PERIOD, // Timeframe MSG_LIB_TEXT_BAR_SPREAD, // Spread MSG_LIB_TEXT_BAR_VOLUME_TICK, // Tick volume MSG_LIB_TEXT_BAR_VOLUME_REAL, // Exchange volume MSG_LIB_TEXT_BAR_TIME, // Period start time MSG_LIB_TEXT_BAR_TIME_YEAR, // Year MSG_LIB_TEXT_BAR_TIME_MONTH, // Month MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR, // Day serial number in a year MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK, // Week day MSG_LIB_TEXT_BAR_TIME_DAY, // Two months MSG_LIB_TEXT_BAR_TIME_HOUR, // Hour MSG_LIB_TEXT_BAR_TIME_MINUTE, // Minute MSG_LIB_TEXT_BAR_INDEX, // Index in timeseries MSG_LIB_TEXT_BAR_HIGH, // Highest price for the period MSG_LIB_TEXT_BAR_LOW, // Lowest price for the period MSG_LIB_TEXT_BAR_CANDLE_SIZE, // Candle size MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY, // Candle body size MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP, // Candle upper wick size MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN, // Candle lower wick size MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP, // Candle body top MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM, // Candle body bottom MSG_LIB_TEXT_BAR_TYPE_BULLISH, // Bullish bar MSG_LIB_TEXT_BAR_TYPE_BEARISH, // Bearish bar MSG_LIB_TEXT_BAR_TYPE_NULL, // Zero bar MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY, // Candle with a zero body MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA, // First, we need to set the required amount of data using SetAmountUsedData() }; //+------------------------------------------------------------------+
und Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Код возврата вне заданного диапазона кодов ошибок","Out of range of error codes return code"}, {"Не удалось создать объект \"Пауза\"","Failed to create \"Pause\" object"}, {"Не удалось создать объект \"Бар\"","Failed to create \"Bar\" object"}, {"Не удалось синхронизировать данные с сервером","Failed to sync data with server"},
...
{"Будет использоваться время действия ордера до конца текущего дня","Order validity time until the end of the current day will be used"}, {"Январь","January"}, {"Февраль","February"}, {"Март","March"}, {"Апрель","April"}, {"Май","May"}, {"Июнь","June"}, {"Июль","July"}, {"Август","August"}, {"Сентябрь","September"}, {"Октябрь","October"}, {"Ноябрь","November"}, {"Декабрь","December"}, {"Воскресение","Sunday"},
...
{"Добавлены условия активации отложенного запроса","Pending request activation conditions added"}, {"Не удалось получить данные бара","Failed to get bar data"}, {"Не удалось получить данные таймсерии","Failed to get timeseries data"}, {"Не удалось добавить объект-бар в список","Failed to add bar object to list"}, {"Бар","Bar"}, {"Таймфрейм","Timeframe"}, {"Спред","Spread"}, {"Тиковый объём","Tick volume"}, {"Биржевой объём","Real volume"}, {"Время начала периода","Period start time"}, {"Год","Year"}, {"Месяц","Month"}, {"Порядковый номер дня в году","Sequence day number in a year"}, {"День недели","Day of week"}, {"День месяца","Day of month"}, {"Час","Hour"}, {"Минута","Minute"}, {"Индекс в таймсерии","Timeseries index"}, {"Наивысшая цена за период","Highest price for the period"}, {"Наименьшая цена за период","Lowest price for the period"}, {"Размер свечи","Candle size"}, {"Размер тела свечи","Candle body size"}, {"Размер верхней тени свечи","Candle upper shadow size"}, {"Размер нижней тени свечи","Candle lower shadow size"}, {"Верх тела свечи","Top of candle body"}, {"Низ тела свечи","Bottom of candle body"}, {"Бычий бар","Bullish bar"}, {"Медвежий бар","Bearish bar"}, {"Нулевой бар","Zero bar"}, {"Свеча с нулевым телом","Candle with zero body"}, {"Сначала нужно установить требуемое количество данных при помощи SetAmountUsedData()","First you need to set required amount of data using SetAmountUsedData()"}, }; //+---------------------------------------------------------------------+
Die Datei für die Dienstfunktionen DELib.mqh befindet sich in \MQL5\Include\DoEasy\Services\DELib.mqh und erhält die Funktion, die einen Monatsnamen zurückgibt und die Funktion, die eine Zeitrahmenbeschreibung zurückgibt:
//+------------------------------------------------------------------+ //| Return month names | //+------------------------------------------------------------------+ string MonthDescription(const int month) { return ( month==1 ? CMessage::Text(MSG_LIB_TEXT_JANUARY) : month==2 ? CMessage::Text(MSG_LIB_TEXT_FEBRUARY) : month==3 ? CMessage::Text(MSG_LIB_TEXT_MARCH) : month==4 ? CMessage::Text(MSG_LIB_TEXT_APRIL) : month==5 ? CMessage::Text(MSG_LIB_TEXT_MAY) : month==6 ? CMessage::Text(MSG_LIB_TEXT_JUNE) : month==7 ? CMessage::Text(MSG_LIB_TEXT_JULY) : month==8 ? CMessage::Text(MSG_LIB_TEXT_AUGUST) : month==9 ? CMessage::Text(MSG_LIB_TEXT_SEPTEMBER) : month==10 ? CMessage::Text(MSG_LIB_TEXT_OCTOBER) : month==11 ? CMessage::Text(MSG_LIB_TEXT_NOVEMBER) : month==12 ? CMessage::Text(MSG_LIB_TEXT_DECEMBER) : (string)month ); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return timeframe description | //+------------------------------------------------------------------+ string TimeframeDescription(const ENUM_TIMEFRAMES timeframe) { return StringSubstr(EnumToString(timeframe),7); } //+------------------------------------------------------------------+
Die Funktion, die einen Monatsnamen zurückgibt, erhält eine Monatsnummer, und ihre Textbeschreibung wird dementsprechend zurückgegeben.
Die Funktion, die den Namen eines Zeitrahmens zurückgibt, erhält einen Zeitrahmen. Als Nächstes wird die Textrepräsentation des Wertes einer Enumeration vom Zeitrahmen verwendet, um eine Teilzeichenkette von der Position 7 bis zum Ende der Zeichenkette abzurufen. Das erhaltene Ergebnis wird als Text zurückgegeben. Zum Beispiel wird der Wert von H1 aus der Textrepräsentation von PERIOD_H1 Stunde Zeitrahmen abgerufen.
Um die Klassen der Bar-Objekte zu speichern, erstellen wir einen neuen Ordner im Objektkatalog der Bibliothek \MQL5\Include\DoEasy\Objekte\Serien\. Dann erstellen wir die Datei Bar.mqh der Klasse CBar im neuen Ordner.
Betrachten wir nun die Auflistung des Klassenkörpers und die Implementierung seiner Methoden:
//+------------------------------------------------------------------+ //| Bar.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\DELib.mqh" //+------------------------------------------------------------------+ //| bar class | //+------------------------------------------------------------------+ class CBar : public CObject { private: MqlDateTime m_dt_struct; // Date structure int m_digits; // Symbol's digits value string m_period_description; // Timeframe string description long m_long_prop[BAR_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[BAR_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[BAR_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the bar's (1) double and (2) string properties are located at int IndexProp(ENUM_BAR_PROP_DOUBLE property) const { return(int)property-BAR_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_BAR_PROP_STRING property) const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL; } //--- Return the bar type (bullish/bearish/zero) ENUM_BAR_BODY_TYPE BodyType(void) const; //--- Calculate and return the size of (1) candle, (2) candle body, //--- (3) upper, (4) lower candle wick, //--- (5) candle body top and (6) bottom double CandleSize(void) const { return(this.High()-this.Low()); } double BodySize(void) const { return(this.BodyHigh()-this.BodyLow()); } double ShadowUpSize(void) const { return(this.High()-this.BodyHigh()); } double ShadowDownSize(void) const { return(this.BodyLow()-this.Low()); } double BodyHigh(void) const { return ::fmax(this.Close(),this.Open()); } double BodyLow(void) const { return ::fmin(this.Close(),this.Open()); } //--- Return the (1) year and (2) month the bar belongs to, (3) week day, //--- (4) bar serial number in a year, (5) day, (6) hour, (7) minute, int TimeYear(void) const { return this.m_dt_struct.year; } int TimeMonth(void) const { return this.m_dt_struct.mon; } int TimeDayOfWeek(void) const { return this.m_dt_struct.day_of_week; } int TimeDayOfYear(void) const { return this.m_dt_struct.day_of_year; } int TimeDay(void) const { return this.m_dt_struct.day; } int TimeHour(void) const { return this.m_dt_struct.hour; } int TimeMinute(void) const { return this.m_dt_struct.min; } public: //--- Set bar's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string bar properties from the properties array long GetProperty(ENUM_BAR_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_BAR_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_BAR_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of the bar supporting the property virtual bool SupportProperty(ENUM_BAR_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_STRING property) { return true; } //--- Return itself CBar *GetObject(void) { return &this;} //--- Set (1) bar symbol, timeframe and index, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); void SetProperties(const MqlRates &rates); //--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CBar objects by all properties (to search for equal bar objects) bool IsEqual(CBar* compared_bar) const; //--- Constructors CBar(){;} CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates); //+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume, //--- (6) bar period start time, (7) year, (8) month the bar belongs to //--- (9) week number since the year start, (10) week number since the month start //--- (11) bar's day, (12) hour, (13) minute, (14) index ENUM_BAR_BODY_TYPE TypeBody(void) const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE); } ENUM_TIMEFRAMES Period(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD); } int Spread(void) const { return (int)this.GetProperty(BAR_PROP_SPREAD); } long VolumeTick(void) const { return this.GetProperty(BAR_PROP_VOLUME_TICK); } long VolumeReal(void) const { return this.GetProperty(BAR_PROP_VOLUME_REAL); } datetime Time(void) const { return (datetime)this.GetProperty(BAR_PROP_TIME); } long Year(void) const { return this.GetProperty(BAR_PROP_TIME_YEAR); } long Month(void) const { return this.GetProperty(BAR_PROP_TIME_MONTH); } long DayOfWeek(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK); } long DayOfYear(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR); } long Day(void) const { return this.GetProperty(BAR_PROP_TIME_DAY); } long Hour(void) const { return this.GetProperty(BAR_PROP_TIME_HOUR); } long Minute(void) const { return this.GetProperty(BAR_PROP_TIME_MINUTE); } long Index(void) const { return this.GetProperty(BAR_PROP_INDEX); } //--- Return bar's (1) Open, (2) High, (3) Low, (4) Close price, //--- size of the (5) candle, (6) body, (7) candle top, (8) bottom, //--- size of the (9) candle upper, (10) lower wick double Open(void) const { return this.GetProperty(BAR_PROP_OPEN); } double High(void) const { return this.GetProperty(BAR_PROP_HIGH); } double Low(void) const { return this.GetProperty(BAR_PROP_LOW); } double Close(void) const { return this.GetProperty(BAR_PROP_CLOSE); } double Size(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE); } double SizeBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_BODY); } double TopBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_BODY_TOP); } double BottomBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_BODY_BOTTOM); } double SizeShadowUp(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP); } double SizeShadowDown(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN); } //--- Return bar symbol string Symbol(void) const { return this.GetProperty(BAR_PROP_SYMBOL); } //+------------------------------------------------------------------+ //| 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 bar object short name virtual string Header(void); //--- }; //+------------------------------------------------------------------+
Betrachten wir die Klasseninhalte, um die Erinnerung an das vorher beschriebene Material aufzufrischen.
Die Eigenschaften im 'private' Abschnitt der Klasse:
Drei Arrays, die die entsprechenden Eigenschaften der Bar-Objekte speichern — ganzzahlige, reelle und Zeichenketten-Objekte.
Die Methoden zur Berechnung des wahren Index der Objekteigenschaft im entsprechenden Array.
Die Methoden zur Berechnung und Rückgabe zusätzlicher Eigenschaften der Bar-Objekte.
Der 'public' Teil der Klasseneigenschaften:
Die Methoden, die den übergebenen Objekt-Eigenschaftswert in die Arrays der Integer-, Real- und String-Eigenschaften schreiben.
Die Methoden, die den Wert einer angeforderten ganzzahlige, reelle oder Zeichenketten-Eigenschaft aus den Arrays zurückgeben.
Die virtuellen Methoden, die das Flag der Unterstützung einer Eigenschaft durch das Objekt für jede der Eigenschaften zurückgeben. Die Methoden sollen in Nachfolgeobjekten des Bar-Objekts implementiert werden und sollten false zurückgeben, falls das Nachfolgeobjekt die angegebene Eigenschaft nicht unterstützt. Im Bar-Objekt werden alle Eigenschaften unterstützt und die Methoden geben true zurück.
Wir diskutierten die gesamte Struktur der Bibliotheksobjekte im ersten Artikel. Hier werden wir kurz auf die Implementierung der übrigen Klassenmethoden eingehen.
Die Klasse verfügt über drei Konstruktoren:
1. Standardmäßig wird der Konstruktor ohne Parameter für eine einfache Deklaration einer Objektklasse mit anschließender Einstellung aller notwendigen Parameter für das erzeugte Objekt verwendet.
2. Der erste parametrische Konstruktor erhält drei Parameter — Symbol, Zeitrahmen und Barindex. Basierend auf diesen drei Parametern erhält er alle Eigenschaften eines einzelnen Taktobjekts aus der Zeitreihe unter Verwendung der ersten Form der Funktion CopyRates():
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to write bar data to the 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]); } //+------------------------------------------------------------------+
Dieser Konstruktor wird zur einmaligen Datenerfassung aus der Zeitreihe unmittelbar bei der Erstellung des Bar-Objekts verwendet.
3. Der zweite parametrische Konstruktor wird verwendet, um ein Bar-Objekt aus dem bereits vorbereiteten Array der Struktur MqlRates zu erzeugen.
Mit anderen Worten, dies bedeutet, dass das Array der MqlRates-Struktur in einer Schleife durchlaufen und ein Objekt durch den Array-Index erzeugt wird:
//+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates) { 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); } //+------------------------------------------------------------------+
Hier wird neben dem Symbol, dem Zeitrahmen und dem Index auch der Link zur MqlRates-Struktur an den Konstruktor übergeben. Das Bar-Objekt wird auf der Grundlage dieser Daten erstellt.
Die virtuelle Methode Compare() ist für den Vergleich zweier Objekte anhand der angegebenen Eigenschaft gedacht. Sie ist in der CObject Basisobjektklasse der Standardbibliothek definiert und sollte null zurückgeben, wenn die Werte gleich sind, und 1/-1, wenn einer der verglichenen Werte höher/niedriger ist. Zum Suchen und Sortieren wird die Methode Search() der Standardbibliothek verwendet. Die Methode sollte in den abgeleiteten Klassen neu definiert werden:
//+------------------------------------------------------------------+ //| Compare CBar objects by all possible properties | //+------------------------------------------------------------------+ int CBar::Compare(const CObject *node,const int mode=0) const { const CBar *bar_compared=node; //--- compare integer properties of two bars if(mode<BAR_PROP_INTEGER_TOTAL) { long value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_INTEGER)mode); long value_current=this.GetProperty((ENUM_BAR_PROP_INTEGER)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare real properties of two bars else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL) { double value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_DOUBLE)mode); double value_current=this.GetProperty((ENUM_BAR_PROP_DOUBLE)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare string properties of two bars else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL+BAR_PROP_STRING_TOTAL) { string value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_STRING)mode); string value_current=this.GetProperty((ENUM_BAR_PROP_STRING)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } return 0; } //+------------------------------------------------------------------+
Die Methode zur Definition von zwei ähnlichen Bar-Objekte wird verwendet, um zwei Objektbalken zu vergleichen. Sie gibt true nur dann zurück, wenn alle Felder der beiden verglichenen Objekte identisch sind:
//+------------------------------------------------------------------+ //| Compare CBar objects by all properties | //+------------------------------------------------------------------+ bool CBar::IsEqual(CBar *compared_bar) const { int beg=0, end=BAR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } beg=end; end+=BAR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } beg=end; end+=BAR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_STRING prop=(ENUM_BAR_PROP_STRING)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } return true; } //+------------------------------------------------------------------+
Die Methode zur Platzierung eines Symbol-, Zeitrahmen- und Bar-Objekt-Indexes in der Zeitreihe:
//+------------------------------------------------------------------+ //| Set bar symbol, timeframe and index | //+------------------------------------------------------------------+ void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { this.SetProperty(BAR_PROP_INDEX,index); this.SetProperty(BAR_PROP_SYMBOL,symbol); this.SetProperty(BAR_PROP_PERIOD,timeframe); this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); this.m_period_description=TimeframeDescription(timeframe); } //+------------------------------------------------------------------+
Abgesehen von der Einstellung der drei genannten Eigenschaften weist die Methode die Anzahl der Nachkommastellen eines Symbolpreises der Variablen m_Ziffern zu und die zeitliche Textbeschreibung der Variablen m_Periodenbeschreibung . Es genügt, sie einmal bei der Erstellung des Bar-Objekts anzugeben.
Die Methode zum Setzen aller Parameter des Bar-Objekts schreibt einfach die Werte aus der der Methode übergebenen Struktur MqlRates in die Objekteigenschaften und berechnet die Parameter weiterer Objekteigenschaften mit den entsprechenden Methoden:
//+------------------------------------------------------------------+ //| Set bar object parameters | //+------------------------------------------------------------------+ void CBar::SetProperties(const MqlRates &rates) { this.SetProperty(BAR_PROP_SPREAD,rates.spread); this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume); this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume); this.SetProperty(BAR_PROP_TIME,rates.time); this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear()); this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth()); this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear()); this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek()); this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay()); this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour()); this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute()); //--- this.SetProperty(BAR_PROP_OPEN,rates.open); this.SetProperty(BAR_PROP_HIGH,rates.high); this.SetProperty(BAR_PROP_LOW,rates.low); this.SetProperty(BAR_PROP_CLOSE,rates.close); this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize()); this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize()); this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh()); this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow()); this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize()); this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize()); //--- this.SetProperty(BAR_PROP_TYPE,this.BodyType()); } //+------------------------------------------------------------------+
Die Methode, die Beschreibungen aller Eigenschaften eines Bar-Objekts anzeigt:
//+------------------------------------------------------------------+ //| Display bar properties in the journal | //+------------------------------------------------------------------+ void CBar::Print(const bool full_prop=false) { ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") ============="); int beg=0, end=BAR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=BAR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=BAR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_STRING prop=(ENUM_BAR_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n"); } //+------------------------------------------------------------------+
Die Beschreibung jeder nächsten Eigenschaft wird durch Objekteigenschaften-Arrays in drei Schleifen angezeigt. Wenn die Eigenschaft nicht unterstützt wird, wird sie nicht im Journal angezeigt, wenn die Eingabe der Methode full_prop false (standardmäßig) ist.
Die Methode, die eine kurze Beschreibung des Bar-Objekts im Journal anzeigt:
//+------------------------------------------------------------------+ //| Display a short bar description in the journal | //+------------------------------------------------------------------+ void CBar::PrintShort(void) { int dg=(this.m_digits>0 ? this.m_digits : 1); string params= ( ::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() ); ::Print(this.Header(),": ",params); } //+------------------------------------------------------------------+
Die Methode zeigt die Beschreibung einer Bar in folgendem Format
Bar "SYMBOL" H4[INDEX]: YYYY.MM.TD HH:MM:SS, O: X.XXXXX, H: X.XXXXX, L: X.XXXXX, C: X.XXXXX, V: XXXX, BAR_TYPE
Zum Beispiel:
Bar "EURUSD" H4[6]: 2020.02.06 20:00:00, O: 1.09749, H: 1.09828, L: 1.09706, C: 1.09827, V: 3323, Bullish bar
Die Methode, die einen kurzen Bar-Namen anzeigt:
//+------------------------------------------------------------------+ //| Return the bar object short name | //+------------------------------------------------------------------+ string CBar::Header(void) { return ( CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+ TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.GetProperty(BAR_PROP_INDEX)+"]" ); } //+------------------------------------------------------------------+
Zeigen wir den Bar-Namen in folgendem Format an
Bar "SYMBOL" H4[INDEX]
Zum Beispiel:
Bar "EURUSD" H4[6]
Die Methode, die die Beschreibung der Ganzzahleigenschaft des Bar-Objekts:
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_INDEX ? CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.BodyTypeDescription() ) : property==BAR_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==BAR_PROP_SPREAD ? CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_TICK ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_REAL ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==BAR_PROP_TIME_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.Year() ) : property==BAR_PROP_TIME_MONTH ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+MonthDescription((int)this.Month()) ) : property==BAR_PROP_TIME_DAY_OF_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.DayOfYear(),3,'0') ) : property==BAR_PROP_TIME_DAY_OF_WEEK ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek()) ) : property==BAR_PROP_TIME_DAY ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Day(),2,'0') ) : property==BAR_PROP_TIME_HOUR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Hour(),2,'0') ) : property==BAR_PROP_TIME_MINUTE ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Minute(),2,'0') ) : "" ); } //+------------------------------------------------------------------+
Die Methode übergibt die Integer-Eigenschaft. Abhängig von ihrem Wert wird ihre in der Datei Datas.mqh gesetzte Textbeschreibung zurückgegeben.
Die Methoden, die Beschreibungen der real- und string-Eigenschaften des Bar-Objekts zurückgeben, sind ähnlich wie die Methode, die die Beschreibung der integer-Eigenschaft des Bar-Objekts zurückgibt:
//+------------------------------------------------------------------+ //| Return the description of the bar's real property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( property==BAR_PROP_OPEN ? CMessage::Text(MSG_ORD_PRICE_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_HIGH ? CMessage::Text(MSG_LIB_TEXT_BAR_HIGH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_LOW ? CMessage::Text(MSG_LIB_TEXT_BAR_LOW)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CLOSE ? CMessage::Text(MSG_ORD_PRICE_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_BODY ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_SHADOW_UP ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_SHADOW_DOWN ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_BODY_TOP ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_BODY_BOTTOM ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : "" ); } //+------------------------------------------------------------------+ //| Return the description of the bar string property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_STRING property) { return(property==BAR_PROP_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL)+": \""+this.GetProperty(property)+"\"" : ""); } //+------------------------------------------------------------------+
Die Methode für die Rückgabe des Bartyps:
//+------------------------------------------------------------------+ //| Return the bar type (bullish/bearish/zero) | //+------------------------------------------------------------------+ ENUM_BAR_BODY_TYPE CBar::BodyType(void) const { return ( this.Close()>this.Open() ? BAR_BODY_TYPE_BULLISH : this.Close()<this.Open() ? BAR_BODY_TYPE_BEARISH : (this.ShadowUpSize()+this.ShadowDownSize()==0 ? BAR_BODY_TYPE_NULL : BAR_BODY_TYPE_CANDLE_ZERO_BODY) ); } //+------------------------------------------------------------------+
Hier ist alles einfach: Wenn der Schlusskurs der Bar den Eröffnungskurs übersteigt, ist dies eine Aufwärtsbar, liegt der Schlusskurs der Bar unter dem Eröffnungskurs (bullish), ist dies eine Abwärtsbar (bearish). Wenn beide Kerzendochte gleich Null sind, handelt es sich um eine Bar mit einem Nullkörper , andernfalls ist dies eine Kerze mit einem Nullkörper.
Die Methode, die die Beschreibung einer Bar zurückgibt:
//+------------------------------------------------------------------+ //| Return the bar type description | //+------------------------------------------------------------------+ string CBar::BodyTypeDescription(void) const { return ( this.BodyType()==BAR_BODY_TYPE_BULLISH ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BULLISH) : this.BodyType()==BAR_BODY_TYPE_BEARISH ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BEARISH) : this.BodyType()==BAR_BODY_TYPE_CANDLE_ZERO_BODY ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY) : CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_NULL) ); } //+------------------------------------------------------------------+
Abhängig vom Typ der Bar gibt die Methode ihre in der Datei Datas.mqh festgelegte Textbeschreibung zurück.
Die Objektklasse Bar ist bereit. Jetzt können wir ein Bar-Objekt für jeden erforderlichen Takt einer erforderlichen Zeitreihe erstellen. Das Objekt allein kann uns jedoch keinen signifikanten Vorteil gegenüber dem üblichen Empfang von Daten über die Anforderung eines Zeitreihen-Balkens mittels CopyRates() verschaffen.
Um die Zeitreihendaten so verwalten zu können, wie wir wollen, müssen wir die Liste der Bar-Objekte erstellen, die der benötigten Zeitreihe in der erforderlichen Menge entsprechen. Dann können wir bereits die Listendaten analysieren und nach allen für die Analyse erforderlichen Daten suchen.
Das bedeutet, dass wir eine Zeitserienliste erstellen müssen, um Bar-Objekte zu speichern.
Außerdem müssen wir die Eröffnung einer neuen Bar erkennen, um der Liste ein weiteres Bar-Objekt hinzuzufügen, und immer ein Werkzeug zur Verfügung haben, das die Eröffnung einer neuen Bar unabhängig von der Anzahl der Symbole und dem Zeitrahmen anzeigt.
Bevor wir eine Liste mit gespeicherten Bar-Objekten erstellen, schreiben wir die Klasse "New bar", da das Klassenobjekt eine der Eigenschaften der Barliste ist.
Ein neues Bar-Objekt
Um das Ereignis der Eröffnung einer neuen Bar zu erkennen, vergleichen wir den Zeitpunkt der Eröffnung der aktuellen Bar mit dem Zeitpunkt der vorherigen Eröffnung. Wenn sie nicht übereinstimmen, wurde eine neue Bar eröffnet. In diesem Fall müssen wir die neue Eröffnungszeit als die vorherige für einen späteren Vergleich speichern:
NewBar = false; if(PrevTime != Time) { NewBar = true; PrevTime = Time; }
In diesem Fall wird das neue Barereignis einmal geöffnet, während alle nachfolgenden Befehle bereits mit der neuen Bar ausgeführt werden.
Manchmal verlangen wir jedoch, dass bestimmte Befehle genau nach dem "Neuen Takt" ausgeführt werden. Das Ereignis sollte so lange relevant bleiben, bis alle Befehle ausgeführt sind. Um dies zu erreichen, müssen wir alle Befehle ausführen, die bis zum Erscheinen eines neuen Balkens abgeschlossen sein sollten, bevor wir dem vorherigen einen neuen Wert zuweisen: Das Ereignis sollte relevant bleiben, bis alle Befehle abgeschlossen sind. Um dies zu erreichen, müssen wir alle Befehle ausführen, die bis zum Erscheinen eines neuen Balkens abgeschlossen sein sollten, bevor wir dem vorherigen einen neuen Wert zuweisen:
NewBar = false; if(PrevTime != Time) { NewBar = true; // ... commands to be // ... executed // ... when a new bar appears PrevTime = Time; }
Somit kann die erste Option als unabhängige Funktion ausgeführt werden, die das neue Flag der Eröffnung des Balkens zurückgibt. Die zweite Option sollte in der vorliegenden Version Teil des OnTick()-Handlers sein, vorzugsweise ganz am Anfang, so dass alle Befehle, die zum Zeitpunkt des Erscheinens der neuen Bar ausgeführt werden sollen, zuerst ausgeführt werden. Ihnen folgen dann alle Befehle, die zu jeder Zeit ausgeführt werden.
Im einfachsten Fall ist dies ausreichend, um das Öffnen einer neuen Bar zu steuern.
Dies reicht jedoch für die Bedürfnisse der Bibliothek nicht aus — wir benötigen eine separate neue Definition der Bars für jedes Symbol und jeden Zeitrahmen in zwei beschriebenen Optionen:
- automatische Steuerung und Zeitersparnis für ein bestimmtes Symbol und einen bestimmten Zeitrahmen (Rückgabe des Ereignis-Flags von "New bar" für jedes Symbol und jeden Zeitrahmen separat),
- Zeitmanagement für ein bestimmtes Symbol und einen bestimmten Zeitrahmen mit manueller Steuerung der Speicherung seines neuen Wertes
(Definition des Ereignisses "New bar" und die Möglichkeit, für jedes Symbol und jeden Zeitrahmen separat festzulegen, wann die neue Zeit für die spätere Kontrolle des nächsten neuen Balkens gespeichert werden soll).
Es wird nicht empfohlen, auf die Zeitreihen-Aktualisierungsfunktionen zuzugreifen, wenn Daten über das aktuelle Symbol und den aktuellen Zeitrahmen angefordert werden. Eine solche Anforderung führt wahrscheinlich zu einem Konflikt, da historische Daten im gleichen Thread aktualisiert werden, in dem der Indikator in arbeitet. Stellen Sie daher sicher, dass Sie den Erhalt der Zeitreihendaten des aktuellen Symbols oder Zeitrahmens prüfen. Wenn ja, verwenden Sie für eine solche Anfrage eine andere Methode, anstatt SeriesInfoInteger() und andere Funktionen, die serielle Daten zurückgeben, für den Zugriff, der mit dem Laden der Historie beginnt.
Eine solche Methode existiert, und sie ist recht einfach:
Die Parameter von OnCalculate() der Indikatoren verfügen bereits über die notwendigen Variablen:
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[])
- rates_total — Anzahl der in der Historie verfügbaren Zeitreihen (ähnlich wie die Funktion Bars() ohne Parameter),
- prev_calculated — Menge der bereits berechneten Daten beim letzten Aufruf,
- time[] — Zeitreihenarray mit Daten zur Barzeit.
Die Indikatoren ermöglichen es Ihnen, Veränderungen der historischen Daten mit Hilfe einer einfachen Berechnung zu verfolgen:
- Wenn (rates_total — prev_calculated) 1 überschreitet, bedeutet dies, dass die Historie geladen wird und der Indikator neu gezeichnet werden sollte,
- wenn (rates_total — prev_calculated) gleich 1 ist, wird ein neuer Balken auf dem Symbol für den aktuellen Zeitrahmen geöffnet.
- normalerweise ist der Ausdruck (rates_total — prev_calculated) bei jedem neuen Tick 0.
In den Indikatoren wird die Taktzeit aus dem Array time[] an die Klassenmethoden für das aktuelle Symbol und den Zeitrahmen übergeben, um einen neuen Takt zu definieren und seine Zeit anzugeben. In anderen Fällen erhalten wir die Zeit innerhalb der Klassenmethoden.
Erstellen wir in \MQL5\Include\DoEasy\Objects\Series\ die neue Datei NewBarObj.mqh für die Klasse CNewBarObj:
//+------------------------------------------------------------------+ //| NewBarObj.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj { private: string m_symbol; // Symbol ENUM_TIMEFRAMES m_timeframe; // Timeframe datetime m_new_bar_time; // New bar time for auto time management datetime m_prev_time; // Previous time for auto time management datetime m_new_bar_time_manual; // New bar time for manual time management datetime m_prev_time_manual; // Previous time for manual time management //--- Return the current bar data datetime GetLastBarDate(const datetime time); public: //--- Set (1) symbol and (2) timeframe void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } void SetPeriod(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } //--- Return (1) symbol and (2) timeframe string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } //--- Return the (1) new bar time datetime TimeNewBar(void) const { return this.m_new_bar_time; } //--- Return the new bar opening flag during the time (1) auto, (2) manual management bool IsNewBar(const datetime time); bool IsNewBarManual(const datetime time); //--- Constructors CNewBarObj(void) : m_symbol(::Symbol()), m_timeframe((ENUM_TIMEFRAMES)::Period()), m_prev_time(0),m_new_bar_time(0), m_prev_time_manual(0),m_new_bar_time_manual(0) {} CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe); }; //+------------------------------------------------------------------+
Ich denke, hier ist alles ganz einfach:
Die Variablen der Klassenmitglieder werden im 'private' Abschnitt zur Speicherung eines Symbols und eines Zeitrahmens deklariert, für den das Objekt das Ereignis "New bar" definieren soll.
Die Variablen für die Speicherung der neuen Eröffnungszeit der Bar und der vorherigen Öffnungszeit sind getrennt für ein automatisches und manuelles Zeitmanagement (wir hatten bereits besprochen, warum wir dies benötigen).
Die Methode GetLastBarDate() gibt die neue Eröffnungszeit der Bar zurück und ist nachfolgend zu betrachten.
Die Methode SaveNewBarTime() zum Speichern der neuen Bar-Zeit, da die vorherige Zeit für die manuelle Zeitverwaltung deklariert und im 'public' Klassenabschnitt implementiert ist. Das ermöglicht es Bibliotheksnutzern, die neue Bar-Zeit zu speichern, nachdem alle erforderlichen Aktionen darauf abgeschlossen sind.
Die übrigen Methoden sind selbsterklärend, und es hat keinen Sinn, hier auf sie einzugehen.
Die Klasse verfügt über zwei Konstruktoren. Der erste Konstruktor hat keine Parameter. Das aktuelle Symbol und der Zeitrahmen werden in seiner Initialisierungsliste festgelegt, während alle neuen Bar-Zeitwerte und die vorherige Bar-Eröffnungszeit zurückgesetzt werden. Nach der Erstellung eines solchen Objekts müssen wir die Methoden zum Setzen des erforderlichen Symbols und Zeitrahmens für das erstellte Klassenobjekt unabhängig voneinander aufrufen.
Der zweite Konstruktor ist parametrisch. Das erforderliche Symbol und der Zeitrahmen werden sofort an ihn gesendet:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),m_timeframe(timeframe) { this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0; } //+------------------------------------------------------------------+
Ein Symbol und ein Zeitrahmen, die den erzeugten Klassenobjektparametern übergeben werden, werden in seiner Initialisierungsliste festgelegt. Anschließend wird allen Zeitvariablen im Klassenkörper Null zugewiesen.
Die Methode, die das Flag einer neuen Bareröffnung während der automatischen Zeitverwaltung zurückgibt:
//+------------------------------------------------------------------+ //| Return new bar opening flag | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBar(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time==0 && this.m_new_bar_time==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time=this.m_prev_time=tm; return false; } //--- If the previous time is not equal to the current bar open time, this is a new bar if(this.m_prev_time!=tm) { //--- set the new bar opening time, //--- set the previous time as the current one and return 'true' this.m_new_bar_time=this.m_prev_time=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
Die Methode gibt true jedes Mal zurück, wenn eine neue Bar auf einem für das Objekt festgelegten Symbol oder Zeitrahmen eröffnet wird.
Die Methode gibt das Flag für das Öffnen einer neuen Bar während der manuellen Zeitverwaltung zurück:
//+------------------------------------------------------------------+ //| Return the new bar opening flag during the manual management | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBarManual(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time_manual==0 && this.m_new_bar_time_manual==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time_manual=this.m_prev_time_manual=tm; return false; } //--- If the previous time is not equal to the current bar open time, this is a new bar if(this.m_prev_time_manual!=tm) { //--- set the new bar opening time and return 'true' //--- Save the previous time as the current one from the program using the SaveNewBarTime() method //--- Till the previous time is forcibly set as the current one from the program, //--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar. this.m_new_bar_time=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
Im Gegensatz zur automatischen Zeitverwaltung weist die Methode die aktuelle neue Bar-Eröffnungszeit nicht der Variablen zu, die die vorherige Zeit der Bar speichert. Dadurch ist es möglich, das Flag für die Eröffnung einer neuen Bar jedes Mal auf einem neuen Tick zu senden, nachdem das Ereignis "New bar" erkannt wurde, bis der Nutzer entscheidet, dass alle bei der Eröffnung eines neuen Taktes auszuführenden Aktionen abgeschlossen sind und es notwendig ist, die Eröffnungszeit der neuen Bar als die vorherige zu speichern.
Die Zeit einer neuen Bar wird an die Eingaben beider Methoden weitergegeben. Die Methode GetLastBarDate() wird dann verwendet, um die zu verwendende Zeit zu definieren:
//+------------------------------------------------------------------+ //| Return the current bar time | //+------------------------------------------------------------------+ datetime CNewBarObj::GetLastBarDate(const datetime time) { return ( ::MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? time : (datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE) ); } //+------------------------------------------------------------------+
Wenn es sich um einen Indikator handelt, stimmen sowohl das Symbol als auch der Zeitrahmen des Objekts "New bar" mit dem aktuellen Symbol und Zeitrahmen überein, die an die Methode übergebene Zeit wird zurückgegeben (bei Indikatoren ist die Zeit in den OnCalculate()-Parametern vorhanden, und zwar im Array time[] — dies ist das Array, von dem aus die Zeit an die Methoden übergeben werden soll, die einen neuen Takt definieren), Andererseits empfangen Sie die Zeit des letzten Taktes mit SeriesInfoInteger() — in diesem Fall kann statt der Zeit ein beliebiger Wert übergeben werden.
Die Objektklasse "New bar" ist bereit für unsere aktuellen Bedürfnisse. Jetzt ist es an der Zeit, die Liste der Barobjekte zu erstellen.
Die Liste der Bar-Objekte ist im Wesentlichen ein Segment von historischen Zeitreihendaten, die Teil von MqlRates sind. Warum brauchen wir eine separate Liste?
Zunächst erhalten wir die schnelle Sortier-, Such- und Vergleichsfunktion. Zweitens liefern die Bar-Objekte, deren angegebene Anzahl in der Liste gespeichert ist, neben den Strukturfeldern MqlRates weitere Felder mit Werten, die in Zukunft die Suche nach verschiedenen Kerzenformationen vereinfachen sollen.
Die Liste der Bar-Objekte, Suchen und Sortieren
Für die Liste der Zeitreihen (Liste der Bar-Objekte) verwenden wir die Klasse der Standardbibliothek der dynamischen Arrays von Zeigern auf Instanzen der Klasse CObject und deren Nachkommen. Im aktuellen Artikel werde ich das einzige Listenobjekt von Bar-Objekten implementieren, um Bars nur einer Zeitreihe nach Symbol und Zeitrahmen zu speichern. In den zukünftigen Artikeln werde ich die Liste als Grundlage für die Sammlung von Zeitreihen nach Zeitrahmen für jedes einzelne im Benutzerprogramm verwendete Symbol verwenden. Auf diese Weise werden wir mehrere gleichartige Sammlungen von Zeitreihenlisten haben, in denen es möglich sein wird, schnell nach den notwendigen Informationen für die Analyse und den Vergleich mit anderen Listen von Zeitreihensammlungen zu suchen, die den Bibliotheksbenutzern zur Verfügung stehen.
Jede Liste mit Bar-Objekten soll eine nutzerdefinierte Anzahl von Bar-Objekten haben (History-Tiefe). Standardmäßig soll die Historietiefe aller Listen eine Anzahl von 1000 Bars haben. Vor ihrer Erstellung sollte jede Liste die Synchronisation der Daten mit dem Handelsserver in Betracht ziehen. Jede Liste jeder Zeitreihe soll einen Wert haben, der die Anzahl der verfügbaren historischen Bars angibt. Dieser Wert wird von der Funktion Bars() ohne Parameter zurückgegeben. Wenn sie Null zurückgibt, ist die Historie noch nicht synchronisiert. Wir werden mehrere Versuche mit kleinen Unterbrechungen zwischen ihnen machen, während wir auf die Datensynchronisation mit dem Server warten.
In der Datei Defines.mqh erstellen wir die Makro-Substitution, die die Tiefe der angewandten Historie definiert, die Anzahl der Pausen-Millisekunden zwischen den Versuchen, die Historie mit dem Server zu synchronisieren und die Anzahl der Versuche, das Synchronisationsereignis zu erhalten:
//--- Pending request type IDs #define PENDING_REQUEST_ID_TYPE_ERR (1) // Type of a pending request created based on the server return code #define PENDING_REQUEST_ID_TYPE_REQ (2) // Type of a pending request created by request //--- Timeseries parameters #define SERIES_DEFAULT_BARS_COUNT (1000) // Required default amount of timeseries data #define PAUSE_FOR_SYNC_ATTEMPTS (16) // Amount of pause milliseconds between synchronization attempts #define ATTEMPTS_FOR_SYNC (5) // Number of attempts to receive synchronization with the server //+------------------------------------------------------------------+ //| Structures | //+------------------------------------------------------------------+
Um die Kollektionsliste schnell durchsuchen und sortieren zu können, haben wir die Funktionen bereits in der Klasse CSelect erstellt, die in der Datei \MQL5\Include\DoEasy\Services\ Ordner der Dienstfunktionen und Klassen der Datei Select.mqh ausgewählt wurde.
Fügen wir die Methoden zum Suchen und Sortieren in den Listen der Bar-Objekte hinzu.
Binden wir die Objektklassendatei der Bar-Objekte in die Auflistung ein:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" #include "..\Objects\Symbols\Symbol.mqh" #include "..\Objects\PendRequest\PendRequest.mqh" #include "..\Objects\Series\Bar.mqh" //+------------------------------------------------------------------+
Fügen wir die Definitionen der Methoden zum Suchen und Sortieren nach den Eigenschaften der Bar-Objekte zum Klassenkörper hinzu:
//+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { private: //--- Method for comparing two values template<typename T> static bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); public: //+------------------------------------------------------------------+ //| Methods of working with orders | //+------------------------------------------------------------------+ //--- Return the list of orders with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the order index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //--- Return the order index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with events | //+------------------------------------------------------------------+ //--- Return the list of events with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with accounts | //+------------------------------------------------------------------+ //--- Return the list of accounts with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with symbols | //+------------------------------------------------------------------+ //--- Return the list of symbols with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the symbol index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property); static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property); static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property); //--- Return the symbol index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property); static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property); static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with pending requests | //+------------------------------------------------------------------+ //--- Return the list of pending requests with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the pending request index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property); static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property); static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property); //--- Return the pending request index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property); static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property); static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with timeseries bars | //+------------------------------------------------------------------+ //--- Return the list of bars with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the pending request index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property); static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property); static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property); //--- Return the pending request index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property); static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property); static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_STRING property); //--- }; //+------------------------------------------------------------------+
Implementieren wir die hinzuzufügenden Methoden außerhalb des Klassenkörpers:
//+------------------------------------------------------------------+ //| Methods of working with timeseries bar lists | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of bars with one integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); int total=list_source.Total(); for(int i=0; i<total; i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of bars with one real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of bars with one string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_INTEGER property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_DOUBLE property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total== 0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_STRING property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+
Wir haben ähnliche Methoden im dritten Artikel der vorhergehenden Serie besprochen, sodass es keinen Sinn macht, hier darauf einzugehen. Wenn Sie Fragen haben, können Sie diese gerne in den Kommentaren stellen.
Wir legen in \MQL5\Include\DoEasy\Objects\Series\ die Datei Series.mqh der Klasse CSeries an und binden die Klassendatei CSelect ein, zusammen mit den neu angelegten Objektklassen "New Bar" und "Bar":
//+------------------------------------------------------------------+ //| Series.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
Fügen wir nun alle erforderlichen Variablen der Klasse hinzu und deklarieren die Klassenmethoden im Klassenkörper:
//+------------------------------------------------------------------+ //| Series.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeries : public CObject { private: ENUM_PROGRAM_TYPE m_program; // Program type ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol uint m_amount; // Amount of applied timeseries data uint m_bars; // Number of bars in history by symbol and timeframe bool m_sync; // Synchronized data flag CArrayObj m_list_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object public: //--- Return the timeseries list CArrayObj* GetList(void) { return &m_list_series;} //--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj* GetList(ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } //--- Set (1) symbol and timeframe and (2) the number of applied timeseries data void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe); bool SetAmountUsedData(const uint amount,const uint rates_total); //--- Return (1) symbol, (2) timeframe, (3) number of applied timeseries data, //--- (4) number of bars in the timeseries, the new bar flag with the (5) auto, (6) manual time management string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } uint AmountUsedData(void) const { return this.m_amount; } uint Bars(void) const { return this.m_bars; } bool IsNewBar(const datetime time) { return this.m_new_bar_obj.IsNewBar(time); } bool IsNewBarManual(const datetime time) { return this.m_new_bar_obj.IsNewBarManual(time); } //--- Return the bar object by index (1) in the list and (2) in the timeseries CBar *GetBarByListIndex(const uint index); CBar *GetBarBySeriesIndex(const uint index); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const uint index,const bool from_series=true); double High(const uint index,const bool from_series=true); double Low(const uint index,const bool from_series=true); double Close(const uint index,const bool from_series=true); datetime Time(const uint index,const bool from_series=true); long TickVolume(const uint index,const bool from_series=true); long RealVolume(const uint index,const bool from_series=true); int Spread(const uint index,const bool from_series=true); //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_new_bar_obj.SaveNewBarTime(time); } //--- Synchronize symbol data with server data bool SyncData(const uint amount,const uint rates_total); //--- (1) Create and (2) update the timeseries list int Create(const uint amount=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); //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0); }; //+------------------------------------------------------------------+
Die Methoden zum Empfang der Liste sind in allen Objektsammlungsklassen vorhanden, die Entwicklung dieser Methoden wurde im dritten Teil der vorhergehenden Serie beschrieben.
Gehen wir die Liste der deklarierten Methoden durch und analysieren ihre Implementierung.
Der erste Klassenkonstruktor hat keine Parameter und wird für die Erstellung der Liste für das aktuelle Symbol und den aktuellen Zeitrahmen verwendet:
//+------------------------------------------------------------------+ //| Constructor 1 (current symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(void) : m_bars(0),m_amount(0),m_sync(false) { this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); this.SetSymbolPeriod(::Symbol(),(ENUM_TIMEFRAMES)::Period()); } //+------------------------------------------------------------------+
Die Anzahl der verfügbaren Bars der Zeitreihen, die Anzahl der in der Liste gespeicherten Bars und das Flag der Datensynchronisation mit dem Server werden in der Initialisierungsliste zurückgesetzt.
Der Programmtyp wird dann gesetzt, die Liste der Bar-Objekte wird gelöscht und dafür wird das Flag der Sortierung nach Taktindex gesetzt. Das aktuelle Symbol und der Zeitrahmen für die Liste wird danach gesetzt.
Stellen wir nach dem Erstellen der Liste der Bar-Objekte sicher, dass wir die Anzahl der verwendeten Bars dafür mit der Methode SetAmountUsedData() oder SyncData(), die die Methode SetAmountUsedData() enthält, festlegen. Im Falle von Indikatoren stellen wir sicher, dass rates_total als zweiten Parameter an die Methode übergeben wird.
Der zweite Konstruktor der Klasse verfügt über drei Eingaben (Symbol, Zeitrahmen und die Größe der Liste der Bar-Objekte) und wird dazu verwendet, die Liste für das angegebene Symbol und den angegebenen Zeitrahmen unter Berücksichtigung der erforderlichen Historientiefe zu erstellen:
//+------------------------------------------------------------------+ //| Constructor 2 (specified symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0) : m_bars(0), m_amount(0),m_sync(false) { this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetAmountUsedData(amount,0); } //+------------------------------------------------------------------+
Die Anzahl der verfügbaren Bars der Zeitreihen, die Anzahl der in der Liste gespeicherten Bars und das Flag der Datensynchronisation mit dem Server werden in der Initialisierungsliste zurückgesetzt.
Der Programmtyp wird dann gesetzt, die Liste der Bar-Objekte wird gelöscht und dafür wird das Flag der Sortierung nach Taktindex gesetzt.
Das aktuelle Symbol und der Zeitrahmen werden anschließend für die Liste gesetzt.
Schließlich wird das Ergebnis der Methode zum Setzen der Anzahl der erforderlichen Balken für die Liste der Bar-Objekte SetAmountUsedData() für das Synchronisationsflag gesetzt. Die Liste erhält die erforderliche Historietiefe, die durch die Eingabevariable amount des Konstruktors angegeben wird.
Nachdem wir die Liste der Bar-Objekte erstellt haben, stellen wir sicher, dass die Synchronisierung mit dem Server mit der Methode SyncData() des Programms überprüfen.
Die Methode zum Festlegen eines Symbols und Zeitrahmens:
//+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); this.m_new_bar_obj.SetSymbol(this.m_symbol); this.m_new_bar_obj.SetPeriod(this.m_timeframe); } //+------------------------------------------------------------------+
Die Methode übergibt ein Symbol und einen Zeitrahmen und die Gültigkeit der übergebenen Werte wird überprüft. Schließlich werden entweder das aktuelle Symbol und der aktuelle Zeitrahmen oder die an die Methode übergebenen Werte gesetzt. Als Nächstes werden das neu in den Variablen gespeicherte Symbol und der Zeitrahmen für das Objekt "New bar" der Barliste gesetzt.
Die Methode, die die Anzahl der verwendeten Zeitreihendaten für die Liste der Bar-Objekte setzt:
//+------------------------------------------------------------------+ //| Set the number of required data | //+------------------------------------------------------------------+ bool CSeries::SetAmountUsedData(const uint amount,const uint rates_total) { //--- Set the number of available timeseries bars this.m_bars= ( //--- If this is an indicator and the work is performed on the current symbol and timeframe, //--- add the rates_total value passed to the method, //--- otherwise, get the number from the environment this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? rates_total : ::Bars(this.m_symbol,this.m_timeframe) ); //--- If managed to set the number of available history, set the amount of data in the list: if(this.m_bars>0) { //--- if zero 'amount' value is passed, //--- use either the default value (1000 bars) or the number of available history bars - the least one of them //--- if non-zero 'amount' value is passed, //--- use either the 'amount' value or the number of available history bars - the least one of them this.m_amount=(amount==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(amount,this.m_bars)); return true; } return false; } //+------------------------------------------------------------------+
Die Methode erhält die notwendige Datenmenge für die Liste der Bar-Objekte und die Gesamtzahl der Bars der aktuellen Zeitreihe (für Indikatoren).
Anschließend wird die Programmart geprüft und die Quelle der Anzahl der verfügbaren Historie für die Variable m_bars definiert — entweder aus dem an die Methode übergebenen Wert (für den Indikator auf dem aktuellen Symbol und Zeitrahmen) oder aus dem Umfeld. Als Nächstes wird definiert, welcher Wert für die Variable m_amount auf der Grundlage der verfügbaren und erforderlichen Historie festgelegt werden soll.
Die Methode zur Synchronisierung von Symbol- und Zeitrahmendaten mit den Serverdaten:
//+------------------------------------------------------------------+ //|Synchronize symbol and timeframe data with server data | //+------------------------------------------------------------------+ bool CSeries::SyncData(const uint amount,const uint rates_total) { //--- If managed to obtain the available number of bars in the timeseries //--- and return the size of the bar object list, return 'true' this.m_sync=this.SetAmountUsedData(amount,rates_total); if(this.m_sync) return true; //--- Data is not yet synchronized with the server //--- Create a pause object CPause *pause=new CPause(); if(pause==NULL) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ)); return false; } //--- Set the pause duration of 16 milliseconds (PAUSE_FOR_SYNC_ATTEMPTS) and initialize the tick counter pause.SetWaitingMSC(PAUSE_FOR_SYNC_ATTEMPTS); pause.SetTimeBegin(0); //--- Make five (ATTEMPTS_FOR_SYNC) attempts to obtain the available number of bars in the timeseries //--- and set the bar object list size int attempts=0; while(attempts<ATTEMPTS_FOR_SYNC && !::IsStopped()) { //--- If data is currently synchronized with the server if(::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED)) { //--- if managed to obtain the available number of bars in the timeseries //--- and set the size of the bar object list, break the loop this.m_sync=this.SetAmountUsedData(amount,rates_total); if(this.m_sync) break; } //--- Data is not yet synchronized. //--- If the pause of 16 ms is over if(pause.IsCompleted()) { //--- set the new start of the next waiting for the pause object //--- and increase the attempt counter pause.SetTimeBegin(0); attempts++; } } //--- Remove the pause object and return the m_sync value delete pause; return this.m_sync; } //+------------------------------------------------------------------+
Die Logik der Methode wird in den Code-Kommentaren beschrieben. Ich glaube, da ist alles klar.
Die Methoden zum Erstellen und Aktualisieren der Liste mit den Bar-Objekten sind in der Auflistung ausführlich kommentiert. Betrachten wir sie in ihrer Gesamtheit, um nicht viel Platz für ihre Beschreibung zu beanspruchen. Wenn Sie Fragen zu diesen Methoden haben, können Sie diese gerne in den Kommentaren stellen:
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeries::Create(const uint amount=0) { //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'amount' value exceeds zero and is not equal to the one already set, //--- while being lower than the available bar number, //--- set the new value of the required history depth for the list else if(amount>0 && this.m_amount!=amount && amount<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetAmountUsedData(amount,0)) return 0; } //--- For the rates[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the bar object list and set the flag of sorting by bar index MqlRates rates[]; ::ArraySetAsSeries(rates,true); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); ::ResetLastError(); //--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,this.m_amount,rates),err=ERR_SUCCESS; if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- Historical data is received in the rates[] array //--- In the rates[] array loop, for(int i=0; i<copied; i++) { //--- create a new bar object out of the current MqlRates structure by the loop index ::ResetLastError(); CBar* bar=new CBar(this.m_symbol,this.m_timeframe,i,rates[i]); if(bar==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())); continue; } //--- If failed to add bar object to the list, //--- display the appropriate message with the error description in the journal if(!this.m_list_series.Add(bar)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header(),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); } } //--- Return the size of the created bar object list return this.m_list_series.Total(); } //+------------------------------------------------------------------+ //| Update timeseries list and data | //+------------------------------------------------------------------+ void CSeries::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) { MqlRates rates[1]; //--- Set the flag of sorting the list of bars by index this.m_list_series.Sort(SORT_BY_BAR_INDEX); //--- If a new bar is present on a symbol and period, if(this.IsNewBarManual(time)) { //--- create a new bar object and add it to the end of the list CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,0); if(new_bar==NULL) return; if(!this.m_list_series.Add(new_bar)) { delete new_bar; return; } //--- if the specified timeseries size exceeds one bar, remove the earliest bar if(this.m_list_series.Total()>1) this.m_list_series.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(time); } //--- Get the index of the last bar in the list and the object bar by the index int index=this.m_list_series.Total()-1; CBar *bar=this.m_list_series.At(index); //--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe, //--- copy price parameters (passed to the method from the outside) to the bar price structure int copied=1; if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period()) { rates[0].time=time; rates[0].open=open; rates[0].high=high; rates[0].low=low; rates[0].close=close; rates[0].tick_volume=tick_volume; rates[0].real_volume=volume; rates[0].spread=spread; } //--- otherwise, get data to the bar price structure from the environment else copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates); //--- If the prices are obtained, set the new properties from the price structure for the bar object if(copied==1) bar.SetProperties(rates[0]); } //+------------------------------------------------------------------+
Um die Liste der Zeitreihen zu erstellen, setzen wir zunächst die Größe der erforderlichen Historie und holen das Flag der Synchronisierung mit dem Server mithilfe der Methode SyncData(). Anschließend rufen wir die Methode Create() auf. Um die Daten der Liste der Bar-Objekte zu aktualisieren, stellen wir sicher, dass bei jedem Tick die Methode Refresh() aufrufen. Die Methode erkennt von sich aus eine neue Taktöffnung und fügt das neue Bar-Objekt der Liste der Zeitreihen hinzu. Das früheste Bar-Objekt wird aus der Liste der Bar-Objekte entfernt, so dass die Listengröße immer auf der durch die SyncData()-Methode festgelegten Wert bleibt.
Wir müssen die Bar-Objekte aus der Zeitreihen holen, um die Daten zu verwalten. Wenn das Flag für die Sortierung nach Index (SORT_BY_BAR_INDEX) für die Zeitreihenliste gesetzt ist, entspricht die Reihenfolge der Bar-Objekte in der Liste der Position der realen Bar in der Zeitreihe. Wenn wir jedoch ein anderes Sortierflag für die Liste setzen, entspricht die Reihenfolge der Objekte in der Liste nicht mehr der Lage der realen Takte in der Zeitreihe — sie sind in aufsteigender Reihenfolge der Eigenschaft angeordnet, nach der die Liste sortiert ist. Daher haben wir zwei Methoden, um Objekte aus der Liste der Bar-Objekte auszuwählen: die Methode, die einen Bar-Objekt nach seinem Index in der Zeitreihe zurückgibt, und die Methode, die ein Bar-Objekt nach seinem Index in der Liste der Bar-Objekte zurückgibt.
Betrachten wir diese beiden Methoden.
Die Methode, die ein Bar-Objekt anhand seines Indexes in der Liste der Bar-Objekte zurückgibt:
//+------------------------------------------------------------------+ //| Return the bar object by index in the list | //+------------------------------------------------------------------+ CBar *CSeries::GetBarByListIndex(const uint index) { return this.m_list_series.At(this.m_list_series.Total()-index-1); } //+------------------------------------------------------------------+
Der Index des erforderlichen Bar-Objekts wird an die Methode übergeben. Der übergebene Index impliziert die gleiche Richtung wie in der Zeitreihe: der Index Null zeigt das letzte Objekt in der Liste an. Die Objekte in der Liste werden jedoch vom Nullindex bis zu list.Total()-1 gespeichert, d.h. um die letzte Bar in der Liste zu erhalten, fordern wir ihn über den Index list.Total()-1 an, während wir, um die äußerste rechte Bar im Diagramm zu erhalten, ihn über den Index 0 anfordern (umgekehrte Indizierung).
Daher wird der Index in der entsprechenden Methode neu berechnet: Die Nummer des übergebenen Index-1 wird von der Listengröße subtrahiert, und das Bar-Objekt wird auf der Grundlage des berechneten Index entsprechend der Indexierungsrichtung wie in der Zeitreihe zurückgegeben.
Die Methode, die ein Bar-Objekt anhand seines Index in der Zeitreihe zurückgibt:
//+------------------------------------------------------------------+ //| 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)); } //+------------------------------------------------------------------+
Der Index des erforderlichen Bar-Objekts wird an die Methode übergeben. Der übergebene Index impliziert die gleiche Richtung wie in der Zeitreihe.
Um das Objekt mit dem gleichen Barindex wie in der Zeitreihe zu erhalten, sollte es durch die Eigenschaft BAR_PROP_INDEX ausgewählt werden. Wenn die Liste der Bar-Objekte die Bar mit dem erforderlichen Index aufweist, weist list ein einzelnes Objekt auf, das zurückgegeben werden sollte.
Wenn kein solches Objekt vorhanden ist, wird NULL zurückgegeben. Im Falle eines Fehlers geben beide Methoden NULL zurück.
Die Methoden, die die grundlegenden Eigenschaften des Bar-Objekts aus der Liste der Bar-Objekte durch den Index zurückgeben:
//+------------------------------------------------------------------+ //| Return bar's Open by the index | //+------------------------------------------------------------------+ double CSeries::Open(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Open() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's High by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::High(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.High() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Low by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::Low(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Low() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Close by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::Close(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Close() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar time by the timeseries index or the list of bars | //+------------------------------------------------------------------+ datetime CSeries::Time(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Time() : 0); } //+-------------------------------------------------------------------+ //|Return bar tick volume by the timeseries index or the list of bars | //+-------------------------------------------------------------------+ long CSeries::TickVolume(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+--------------------------------------------------------------------+ //|Return bar real volume by the timeseries index or the list of bars | //+--------------------------------------------------------------------+ long CSeries::RealVolume(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar spread by the timeseries index or the list of bars | //+------------------------------------------------------------------+ int CSeries::Spread(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Spread() : WRONG_VALUE); } //+------------------------------------------------------------------+
Die Methoden erhalten den Barindex und das Flag, das anzeigt, dass der angeforderte Index der Indexierungsrichtung entspricht (true), wie in der Zeitreihe.
Basierend auf dem Wert des Flags, erhalten wir das Bar-Objekt entweder mit der Methode GetBarBySeriesIndex() oder GetBarByListIndex(). Dann gibt den Wert der Eigenschaft zurück, die in der Methode angefordert wurde.
Die übrigen hier nicht betrachteten Klassenmethoden von CSeries werden zum Setzen oder Zurückgeben der Werte von Klassenvariablen verwendet.
Um zu überprüfen, was wir im aktuellen Artikel getan haben, müssen wir das externe Programm über die erstellten Klassen informieren. Um dies zu erreichen, binden wir einfach die Klassendatei CSeries in die Hauptobjektdatei der CEngine-Bibliothek ein:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Services\TimerCounter.mqh" #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Collections\SymbolsCollection.mqh" #include "Collections\ResourceCollection.mqh" #include "TradingControl.mqh" #include "Objects\Series\Series.mqh" //+------------------------------------------------------------------+
Jetzt sind wir in der Lage, die Variable der Klasse CSeries im Test-EA zu definieren, um die Listen der Zeitreihen mit der angegebenen Anzahl von Bar-Objekten zu erstellen und zu verwenden (in der aktuellen Implementierung werden die Listen nur zum Testen verwendet). Machen wir das jetzt.
Tests
Um den Test durchzuführen, verwenden wir den EA aus dem letzten Artikel der vorherigen Serie und speichern ihn in \MQL5\Experts\TestDoEasy\Part35\ unter dem Namen TestDoEasyPart35.mq5.
Erstellen wir zwei Variablen (zum Testen) des Objekts der Klasse CSeries — für M1 (2 Balken) und den aktuellen Zeitrahmen (10 Balken). Wir setzen alle erforderlichen Parameter in OnInit() und zeigen drei Listen:
- die Liste aller Bars der aktuellen Zeitreihe, sortiert nach Kerzengröße (von Hoch bis Tief der Bar) — kurze Beschreibungen der Bar-Objekte;
- die Liste aller Bars der aktuellen Zeitreihe, sortiert nach Barindizes (von Bar 0 bis 9) — Kurzbeschreibungen der Bar-Objekte;
- die Liste aller Eigenschaften der Bar-Objekte, die dem entsprechenden Index 1 (vorherige Bar) der aktuellen Zeitreihe entsprechen — vollständige Liste aller Bareigenschaften.
Aktualisieren wir in OnTick() beide Zeitreihen bei jedem Tick und zeigen den Eintrag zum Öffnen einer neuen Bar in jeder der beiden Zeitreihen dieser beiden Zeitreihenlisten an — М1 und die aktuelle. Spielen wir neben dem Journaleintrag den Standardton "news.wav" ab, wenn wir eine neue Bar für die aktuelle Zeitreihe öffnen.
Definieren wir in der Liste der globalen EA-Variablen die beiden Variablen des Klassentyps CSeries — die Variable der Liste der Zeitreihen des aktuellen Zeitrahmens und die Variable der Liste der Zeitreihen M1:
//+------------------------------------------------------------------+ //| TestDoEasyPart35.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_PROFIT_WITHDRAWAL, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20) //--- structures struct SDataButt { string name; string text; }; //--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpDistancePReq = 50; // Distance for Pending Request's activate (points) input uint InpBarsDelayPReq = 5; // Bars delay for Pending Request's activate (current timeframe) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds //--- global variables CEngine engine; CSeries series; CSeries series_m1; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint distance_pending_request; uint bars_delay_pending_request; uint slippage; bool trailing_on; bool pressed_pending_buy; bool pressed_pending_buy_limit; bool pressed_pending_buy_stop; bool pressed_pending_buy_stoplimit; bool pressed_pending_close_buy; bool pressed_pending_close_buy2; bool pressed_pending_close_buy_by_sell; bool pressed_pending_sell; bool pressed_pending_sell_limit; bool pressed_pending_sell_stop; bool pressed_pending_sell_stoplimit; bool pressed_pending_close_sell; bool pressed_pending_close_sell2; bool pressed_pending_close_sell_by_buy; bool pressed_pending_delete_all; bool pressed_pending_close_all; bool pressed_pending_sl; bool pressed_pending_tp; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; double g_point; int g_digits; //+------------------------------------------------------------------+
In OnInit() des EA setzen wir die notwendigen Eigenschaften für die beiden Variablen der Zeitreihenobjekte und zeigen die gesamten Daten auf der erstellten Liste der Bar-Objekte des aktuellen Zeitrahmens an. Bei М1 zeigen wir einfach die Meldung über die erfolgreiche Listenerstellung an:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq); bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq); g_point=SymbolInfoDouble(NULL,SYMBOL_POINT); g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS); //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Reset states of the buttons for working using pending requests for(int i=0;i<14;i++) { ButtonState(butt_data[i].name+"_PRICE",false); ButtonState(butt_data[i].name+"_TIME",false); } //--- Check playing a standard sound by macro substitution and a custom sound by description engine.PlaySoundByDescription(SND_OK); Sleep(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2")); //--- Set the M1 timeseries object parameters series_m1.SetSymbolPeriod(Symbol(),PERIOD_M1); //--- If symbol and M1 timeframe data are synchronized if(series_m1.SyncData(2,0)) { //--- create the timeseries list of two bars (the current and previous ones), //--- if the timeseries is created, display the appropriate message in the journal int total=series_m1.Create(2); if(total>0) Print(TextByLanguage("Создана таймсерия М1 с размером ","Created timeseries M1 with size "),(string)total); } //--- Check filling price data on the current symbol and timeframe series.SetSymbolPeriod(Symbol(),(ENUM_TIMEFRAMES)Period()); //--- If symbol and the current timeframe data are synchronized if(series.SyncData(10,0)) { //--- create the timeseries list of ten bars (bars 0 - 9), //--- if the timeseries is created, display three lists: //--- 1. the list of bars sorted by candle size (from bars' High to Low) //--- 2. the list of bars sorted by bar indices (according to their sequence in the timeseries) //--- 3. the full list of all properties of the previous bar object (bar properties with the timeseries index of 1) int total=series.Create(10); if(total>0) { CArrayObj *list=series.GetList(); CBar *bar=NULL; //--- Display short properties of the bar list by the candle size Print("\n",TextByLanguage("Бары, сортированные по размеру свечи от High до Low:","Bars sorted by candle size from High to Low:")); list.Sort(SORT_BY_BAR_CANDLE_SIZE); for(int i=0;i<total;i++) { bar=series.GetBarByListIndex(i); if(bar==NULL) continue; bar.PrintShort(); } //--- Display short properties of the bar list by the timeseries index Print("\n",TextByLanguage("Бары, сортированные по индексу таймсерии:","Bars sorted by timeseries index:")); list.Sort(SORT_BY_BAR_INDEX); for(int i=0;i<total;i++) { bar=series.GetBarByListIndex(i); if(bar==NULL) continue; bar.PrintShort(); } //--- Display all bar 1 properties Print(""); list=CSelect::ByBarProperty(list,BAR_PROP_INDEX,1,EQUAL); if(list.Total()==1) { bar=list.At(0); bar.Print(); } } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
In OnTick() aktualisieren wir bei jedem Tick die Zeitreihen der Objekte der Klasse CSeries , bei dem Ereignis "New bar", für jede der beiden Listen die Meldungen zu diesem Ereignis anzeigt. zusätzlich den Ton des Ereignisses neue Bar im aktuellen Zeitrahmen abspielen:
//+------------------------------------------------------------------+ //| 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 } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } //--- Check the update of the current and M1 timeseries series.Refresh(); if(series.IsNewBar(0)) { Print("New bar on ",series.Symbol()," ",TimeframeDescription(series.Period())," ",TimeToString(series.Time(0))); engine.PlaySoundByDescription(SND_NEWS); } series_m1.Refresh(); if(series_m1.IsNewBar(0)) { Print("New bar on ",series_m1.Symbol()," ",TimeframeDescription(series_m1.Period())," ",TimeToString(series_m1.Time(0))); } } //+------------------------------------------------------------------+
Kompilieren Sie den EA und starten Sie ihn auf dem Chart.
Zuerst zeigt das Journal Einträge zu Beginn der Erstellung der M1-Zeitserien von zwei Bars, dann die Liste der Bars sortiert nach Kerzengröße, dann die Liste der Bars sortiert in der Reihenfolge der Barindizes in der Zeitreihe und schließlich alle Eigenschaften der Bar-Objekte mit dem Index 1 in der Zeitreihe:
Account 15585535: Artyom Trishkin (MetaQuotes Software Corp.) 9999.40 USD, 1:100, Demo account MetaTrader 5 Work only with the current symbol. The number of symbols used: 1 Created timeseries M1 with size 2 Bars, sorted by size candle from High to Low: Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar Bars, sorted by timeseries index: Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar ============= The beginning of the event parameter list (Bar "EURUSD" H1[1]) ============= Timeseries index: 1 Type: Bearish bar Timeframe: H1 Spread: 1 Tick volume: 1753 Real volume: 0 Period start time: 2020.02.12 11:00:00 Sequence day number in the year: 042 Year: 2020 Month: February Day of week: Wednesday Day od month: 12 Hour: 11 Minute: 00 ------ Price open: 1.09215 Highest price for the period: 1.09232 Lowest price for the period: 1.09114 Price close: 1.09202 Candle size: 0.00118 Candle body size: 0.00013 Top of the candle body: 1.09215 Bottom of the candle body: 1.09202 Candle upper shadow size: 0.00017 Candle lower shadow size: 0.00088 ------ Symbol: "EURUSD" ============= End of the parameter list (Bar "EURUSD" H1[1]) =============
Lassen wir nun den EA im visuellen Tester-Modus auf М5 starten und einen Blick auf die Meldungen des Tester-Journals über das Öffnen neuer Bars werfen:
Wie wir sehen können, geht es in jeder fünften Nachricht um die Eröffnung einer neuen Bar auf М5, unterbrochen von den Nachrichten über die Eröffnung einer neuen Bar auf М1.
Was kommt als Nächstes?
Im nächsten Artikel werden wir die Kollektionsklasse der Bar-Listen erstellen.
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.
Artikel aus der vorhergehenden Serie:
Teil 1. Konzept, Datenverwaltung.
Teil 2. Erhebung (Collection) historischer Aufträge und Deals.
Teil 3. Erhebung (Collection) von Marktorders und Positionen, Organisieren der Suche
Teil 4. Handelsereignisse. Konzept
Teil 5. Klassen und Kollektionen von Handelsereignissen. Senden von Ereignissen an das Programm
Teil 6. Ereignisse auf Netting-Konten
Teil 7. Ereignis der Aktivierung einer StopLimit-Order, Vorbereiten der Funktionsweise bei Änderungen von Orders und Positionen
Teil 8. Ereignisse von Änderungen von Orders und Positionen
Teil 9. Kompatibilität mit MQL4 — Datenvorbereitung
Teil 10. Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und Aktivierung von Pending-Orders
Teil 11. Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen
Teil 12. Objektklasse "Account" und die Kollektion von Konto-Objekten
Teil 13. Das Objekt der Kontoereignisse
Teil 14. Das Symbolobjekt
Teil 15. Die Kollektion der Symbolobjekte
Teil 16. Ereignisse der Kollektionssymbole
Teil 17. Interaktivität von Bibliotheksobjekten
Teil 18. Interaktivität des Kontos und aller anderen Bibliotheksobjekt
Teil 19. Klassenbibliothek für Nachrichten
Teil 20. Erstellen und Speichern von Programmressourcen
Teil 21. Handelsklassen - Plattformübergreifendes Basis-Handelsobjekt
Teil 22. Handelsklassen - Basisklasse des Handels, Verifikation der Einschränkungen
Teil 23. Handelsklasse - Basisklasse des Handels, Verifikation der Parameter
Teil 24. Trading classes - Handelsklassen, automatische Korrektur ungültiger Parametern
Teil 25. Handelsklassen - Basisklasse des Handels, Behandlung der Fehlermeldungen vom Server
Teil 26. Arbeiten mit schwebenden Handelsanfragen - Erste Implementation (Öffnen von Positionen)
Teil 27. Arbeiten mit schwebenden Handelsanfragen - Platzieren von Pending-Orders
Teil 28. Arbeiten mit schwebenden Handelsanfragen - Schließen, Entfernen und Ändern
Teil 29. Arbeiten mit schwebenden Handelsanfragen - Die Klasse der Anfrageobjekte
Teil 30. Schwebende Handelsanfragen - Die Behandlung der Anfrageobjekte
Teil 31. Schwebende Handelsanfragen - Positionseröffnungen unter bestimmten Bedingungen
Teil 32. Schwebende Handelsanfragen - Platzieren von Pending-Orders unter bestimmten Bedingungen
Teil 33. Schwebende Handelsanfragen - Positionen unter bestimmten Bedingungen schließen (ganz, teilweise oder durch eine entgegengesetzte P.)
Teil 34. Schwebende Handelsanfragen - Entfernen und Ändern von Orders und Positionen unter bestimmten Bedingungen