
DoEasy. Dienstfunktionen (Teil 3): Das Muster der „Outside Bar“
Inhalt
Konzept
Fahren wir fort mit dem Abschnitt der DoEasy-Bibliothek über den Umgang mit Preismustern.
Im vorangegangenen Artikel haben wir die Suche und Anzeige von Preismuster mit zwei Balken, der „Inside Bar“ erstellt. Hier werden wir eine Suche nach dem Muster „Outside Bar“ erstellen, das im Wesentlichen ein Spiegelbild des „Inside Bar“ ist.
Aber es gibt auch Unterschiede. Wenn es sich bei „Inside Bars“ um ein bidirektionales Muster handelt und der Einstieg auf beiden Seiten des Musters möglich ist, werden „Outside Bars“ in zwei Richtungen unterteilt - auf- und abwärts:
- BUOVB (Bullish Outside Vertical Bar) - steigender, äußerer, vertikaler Balken. Der Signalbalken überdeckt den vorhergehenden vollständig, sein Schlusskurs ist höher als das Maximum des vorhergehenden Balkens. Schließen Sie einen Handel beim Durchbruch des Hochs des Signalbalkens + Filter (5-10 Punkte) ab.
- BEOVB (Bearish Outside Vertical Bar) - fallender, äußerer, vertikaler Balken. Der Signalbalken überdeckt den vorhergehenden vollständig, sein Schlusskurs ist niedriger als das Minimum des vorhergehenden Balkens. Der Einstieg erfolgt beim Durchbrechen des Minimums des Signalbalkens - Filter (5-10 Punkte).
Bevor wir mit der Erstellung der Klasse des Musters beginnen, müssen wir einige Änderungen an den bereits vorhandenen Bibliotheksklassen vornehmen. Erstens sind nicht alle Methoden in den Musterzugriffsklassen optimal angelegt - dies war nur ein Konzepttest, bei dem alles „frontal“ durch viele Methoden implementiert wurde, jede für ihre eigene Art von Mustern. Jetzt werden wir nur eine Methode für den Zugriff auf die Arbeit mit den angegebenen Mustern erstellen. Für jede der Aktionen verwenden wir eine eigene Methode, bei der das erforderliche Muster durch eine Variable angegeben wird. Dadurch werden die Klassencodes erheblich vereinfacht und verkürzt.
Zweitens sind während der langen Zeit, in der nicht an der Bibliothek gearbeitet wurde, einige Änderungen und Ergänzungen in der MQL5-Sprache vorgenommen worden (noch sind nicht alle angekündigten Änderungen in die Sprache aufgenommen worden), und wir werden diese Änderungen in die Bibliothek aufnehmen. Ich habe auch einige Fehler behoben, die während der gesamten Zeit der Bibliothekstests gefunden wurden. Ich werde alle vorgenommenen Verbesserungen beschreiben.
Verbesserung der Bibliotheksklassen
Bei der Bestimmung einiger Muster ist die gegenseitige Beziehung zwischen den Größen benachbarter Kerzen wichtig, die an der Bildung der Musterfigur beteiligt sind. Fügen wir den Mustereigenschaften einen neuen Wert hinzu, um das Größenverhältnis zu bestimmen. In den Eigenschaften jeder Muster- und Musterverwaltungsklasse ergänzen wir eine Eigenschaft, die den Wert angibt, nach dem das Verhältnis der Kerzen gesucht wird.
In der Bibliotheksdatei \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Aufzählung der Materialeigenschaften des Musters, fügen wir neue Eigenschaften hinzu und erhöhen die Gesamtzahl der Eigenschaften von 10 auf 12:
//+------------------------------------------------------------------+ //| Pattern real properties | //+------------------------------------------------------------------+ enum ENUM_PATTERN_PROP_DOUBLE { //--- bar data PATTERN_PROP_BAR_PRICE_OPEN = PATTERN_PROP_INTEGER_TOTAL,// Pattern defining bar Open price PATTERN_PROP_BAR_PRICE_HIGH, // Pattern defining bar High price PATTERN_PROP_BAR_PRICE_LOW, // Pattern defining bar Low price PATTERN_PROP_BAR_PRICE_CLOSE, // Pattern defining bar Close price PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE, // Percentage ratio of the candle body to the full size of the candle PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the upper shadow size to the candle size PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the lower shadow size to the candle size PATTERN_PROP_RATIO_CANDLE_SIZES, // Ratio of pattern candle sizes PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the candle body to the full candle size in % PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the maximum shadow to the candle size in % PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the minimum shadow to the candle size in % PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION, // Defined criterion for the ratio of pattern candle sizes }; #define PATTERN_PROP_DOUBLE_TOTAL (12) // Total number of real pattern properties #define PATTERN_PROP_DOUBLE_SKIP (0) // Number of pattern properties not used in sorting
Ab der Beta-Version 4540 des MetaTrader 5 Client-Terminals bietet die Enumeration ENUM_SYMBOL_SWAP_MODE den Schlüssel „SYMBOL_SWAP_MODE_CURRENCY_PROFIT“.
Wenn die Funktion SymbolInfoInteger() einen solchen Wert zurückgibt, werden die Swaps auf dem Konto in der Gewinnwährung berechnet.
Fügen wir diesen Wert zu den Bibliotheksdateien hinzu. In \MQL5\Include\DoEasy\Data.mqh geben wir die Indizes der neuen Bibliotheksmeldungen ein:
MSG_SYM_SWAP_MODE_CURRENCY_MARGIN, // Swaps charged in money in symbol margin currency MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT, // Swaps charged in money in client deposit currency MSG_SYM_SWAP_MODE_CURRENCY_PROFIT, // Swaps charged in money in profit calculation currency MSG_SYM_SWAP_MODE_INTEREST_CURRENT, // Swaps charged as specified annual interest from symbol price at calculation of swap MSG_SYM_SWAP_MODE_INTEREST_OPEN, // Swaps charged as specified annual interest from position open price
...
MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE, // Percentage ratio of the candle body to the full size of the candle MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the upper shadow size to the candle size MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the lower shadow size to the candle size MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES, // Ratio of pattern candle sizes MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the candle body to the full candle size in % MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the maximum shadow to the candle size in % MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the minimum shadow to the candle size in % MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION, // Defined criterion for the ratio of pattern candle sizes MSG_LIB_TEXT_PATTERN_NAME, // Name
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Свопы начисляются в деньгах в маржинальной валюте символа","Swaps charged in money in symbol margin currency"}, {"Свопы начисляются в деньгах в валюте депозита клиента","Swaps charged in money in client deposit currency"}, {"Свопы начисляются в деньгах в валюте расчета прибыли","Swaps are charged in money, in profit calculation currency"}, { "Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа", "Swaps are charged as the specified annual interest from the instrument price at calculation of swap" }, {"Свопы начисляются в годовых процентах от цены открытия позиции по символу","Swaps charged as specified annual interest from open price of position"},
...
{"Отношение размера верхней тени к размеру свечи в %","Ratio of the size of the upper shadow to the size of the candle in %"}, {"Отношение размера нижней тени к размеру свечи в %","Ratio of the size of the lower shadow to the size of the candle in %"}, {"Отношение размеров свечей паттерна","Ratio of pattern candle sizes"}, {"Установленный критерий отношения тела свечи к полному размеру свечи в %","Criterion for the Ratio of candle body to full candle size in %"}, {"Установленный критерий отношения размера наибольшей тени к размеру свечи в %","Criterion for the Ratio of the size of the larger shadow to the size of the candle in %"}, {"Установленный критерий отношения размера наименьшей тени к размеру свечи в %","Criterion for the Ratio of the size of the smaller shadow to the size of the candle in %"}, {"Установленный критерий отношения размеров свечей паттерна","Criterion for the Ratio of pattern candle sizes"}, {"Наименование","Name"},
Fügen wir die Beschreibung von zwei neuen Laufzeitfehlern mit den Codes 4306 und 4307 zum Array der Laufzeitfehlermeldungen hinzu:
//+------------------------------------------------------------------+ //| Array of runtime error messages (4301 - 4307) | //| (MarketInfo) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_market[][TOTAL_LANG]= { {"Неизвестный символ","Unknown symbol"}, // 4301 {"Символ не выбран в MarketWatch","Symbol not selected in MarketWatch"}, // 4302 {"Ошибочный идентификатор свойства символа","Wrong identifier of symbol property"}, // 4303 {"Время последнего тика неизвестно (тиков не было)","Time of the last tick not known (no ticks)"}, // 4304 {"Ошибка добавления или удаления символа в MarketWatch","Error adding or deleting a symbol in MarketWatch"}, // 4305 {"Превышен лимит выбранных символов в MarketWatch","Exceeded the limit of selected symbols in MarketWatch"}, // 4306 { "Неправильный индекс сессии при вызове функции SymbolInfoSessionQuote/SymbolInfoSessionTrade", // 4307 "Wrong session ID when calling the SymbolInfoSessionQuote/SymbolInfoSessionTrade function" }, };
In \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh fügen wir die neuen Werte der Enumeration der Methode hinzu, die die Anzahl der Nachkommastellen in Abhängigkeit von der Swap-Berechnungsmethode zurückgibt:
//+------------------------------------------------------------------+ //| Return the number of decimal places | //| depending on the swap calculation method | //+------------------------------------------------------------------+ int CSymbol::SymbolDigitsBySwap(void) { return ( this.SwapMode()==SYMBOL_SWAP_MODE_POINTS || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID ? this.Digits() : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ? this.DigitsCurrency(): this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN ? 1 : 0 ); }
Auch in der Methode, die die Beschreibung des Swap-Berechnungsmodells zurückgibt, geben wir die Rückgabe einer Zeichenkette mit einer Beschreibung des neuen Swap-Berechnungsmodus ein:
//+------------------------------------------------------------------+ //| Return the description of a swap calculation model | //+------------------------------------------------------------------+ string CSymbol::GetSwapModeDescription(void) const { return ( this.SwapMode()==SYMBOL_SWAP_MODE_DISABLED ? CMessage::Text(MSG_SYM_SWAP_MODE_DISABLED) : this.SwapMode()==SYMBOL_SWAP_MODE_POINTS ? CMessage::Text(MSG_SYM_SWAP_MODE_POINTS) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_SYMBOL) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_MARGIN) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_PROFIT) : this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT ? CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_CURRENT) : this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN ? CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_OPEN) : this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT ? CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_CURRENT) : this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID ? CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_BID) : CMessage::Text(MSG_SYM_MODE_UNKNOWN) ); }
In der MQL4-Definitionsdatei \MQL5\Include\DoEasy\ToMQL4.mqh fügen wir eine neue Eigenschaft zur Enumeration der Methoden für die Berechnung von Swaps beim Übertragen einer Position hinzu:
//+------------------------------------------------------------------+ //| Swap charging methods during a rollover | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_SWAP_MODE { SYMBOL_SWAP_MODE_POINTS, // (MQL5 - 1, MQL4 - 0) Swaps are charged in points SYMBOL_SWAP_MODE_CURRENCY_SYMBOL, // (MQL5 - 2, MQL4 - 1) Swaps are charged in money in symbol base currency SYMBOL_SWAP_MODE_INTEREST_OPEN, // (MQL5 - 6, MQL4 - 2) Swaps are charged as the specified annual interest from the open price of position SYMBOL_SWAP_MODE_CURRENCY_MARGIN, // (MQL5 - 3, MQL4 - 3) Swaps are charged in money in margin currency of the symbol SYMBOL_SWAP_MODE_DISABLED, // (MQL5 - 0, MQL4 - N) No swaps SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT, // Swaps are charged in money, in client deposit currency SYMBOL_SWAP_MODE_INTEREST_CURRENT, // Swaps are charged as the specified annual interest from the instrument price at calculation of swap SYMBOL_SWAP_MODE_REOPEN_CURRENT, // Swaps are charged by reopening positions by the close price SYMBOL_SWAP_MODE_REOPEN_BID, // Swaps are charged by reopening positions by the current Bid price SYMBOL_SWAP_MODE_CURRENCY_PROFIT // Swaps charged in money in profit calculation currency };
Wir korrigieren in der Bibliotheksbasisklassendatei \MQL5\Include\DoEasy\Objects\BaseObj.mqh die Strings mit potenziellen Speicherlecks:
//+------------------------------------------------------------------+ //| Add the event object to the list | //+------------------------------------------------------------------+ bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam) { CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam); if(event==NULL) return false; this.m_list_events.Sort(); if(this.m_list_events.Search(event)>WRONG_VALUE) { delete event; return false; } return this.m_list_events.Add(event); } //+------------------------------------------------------------------+ //| Add the object base event to the list | //+------------------------------------------------------------------+ bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value) { CBaseEvent* event=new CBaseEvent(event_id,reason,value); if(event==NULL) return false; this.m_list_events_base.Sort(); if(this.m_list_events_base.Search(event)>WRONG_VALUE) { delete event; return false; } return this.m_list_events_base.Add(event); }
Hier geben die Methoden das Ergebnis des Hinzufügens des Objekts zur Liste zurück (true - erfolgreich hinzugefügt, false - Fehler). Tritt ein Fehler beim Hinzufügen des Objekts auf, das mit dem Befehl new erstellten wurde, verbleibt das Objekt irgendwo im Speicher, ohne dass der Zeiger darauf in der Liste gespeichert wird, was zu Speicherlecks führt. Beheben wir das:
//+------------------------------------------------------------------+ //| Add the event object to the list | //+------------------------------------------------------------------+ bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam) { CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam); if(event==NULL) return false; this.m_list_events.Sort(); if(this.m_list_events.Search(event)>WRONG_VALUE || !this.m_list_events.Add(event)) { delete event; return false; } return true; } //+------------------------------------------------------------------+ //| Add the object base event to the list | //+------------------------------------------------------------------+ bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value) { CBaseEvent* event=new CBaseEvent(event_id,reason,value); if(event==NULL) return false; this.m_list_events_base.Sort(); if(this.m_list_events_base.Search(event)>WRONG_VALUE || !this.m_list_events_base.Add(event)) { delete event; return false; } return true; }
Wenn nun das Hinzufügen eines Ereignisobjekts zur Liste fehlschlägt, wird das neu erstellte Objekt auf die gleiche Weise gelöscht, wie wenn das gleiche Objekt bereits in der Liste wäre, und die Methode gibt false zurück.
In \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh muss bei der Berechnung der Anzahl der Punkte eine Rundung des erhaltenen Ergebnisses hinzugefügt werden, da andernfalls bei Werten, die kleiner als 1 sind, aber nahe bei 1 liegen, bei der Umwandlung einer reellen Zahl in eine ganze Zahl eine Anzahl von Punkten von Null entsteht:
//+------------------------------------------------------------------+ //| Get order profit in points | //+------------------------------------------------------------------+ int COrder::ProfitInPoints(void) const { MqlTick tick={0}; string symbol=this.Symbol(); if(!::SymbolInfoTick(symbol,tick)) return 0; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder(); double point=::SymbolInfoDouble(symbol,SYMBOL_POINT); if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0; if(this.Status()==ORDER_STATUS_HISTORY_ORDER) return int(type==ORDER_TYPE_BUY ? (::round(this.PriceClose()-this.PriceOpen())/point) : type==ORDER_TYPE_SELL ? ::round((this.PriceOpen()-this.PriceClose())/point) : 0); else if(this.Status()==ORDER_STATUS_MARKET_POSITION) { if(type==ORDER_TYPE_BUY) return (int)::round((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL) return (int)::round((this.PriceOpen()-tick.ask)/point); } else if(this.Status()==ORDER_STATUS_MARKET_PENDING) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT) return (int)fabs(::round((tick.bid-this.PriceOpen())/point)); else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT) return (int)fabs(::round((this.PriceOpen()-tick.ask)/point)); } return 0; }
In \MQL5\Include\DoEasy\Objects\Events\Event.mqh gab es einen Fehler bei der Rückgabe eines Wertes vom Typ long als Wert einer Enumeration:
//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket, //--- (3) current position order type, (4) current position order ticket, //--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);} ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); }
Während der Wert des Tickets eines Auftrags vom Typ long den Wert INT_MAX (aus der Enumeration des Datentyps int) nicht überschritt, wurden der Wert des Tickets korrekt zurückgegeben. Sobald der Ticketwert jedoch INT_MAX überschritt, kam es zum Überlauf und es wurde eine negative Zahl zurückgegeben. Jetzt ist alles geregelt:
//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket, //--- (3) current position order type, (4) current position order ticket, //--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT); } ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); }
In der Enumeration, die den Namen des Ereignisstatus des Auftragssystems zurückgibt, fehlte der Änderungsstatus, und in einigen Fällen wurde der Status in der Beschreibung des Handelsereignisses als „Unbekannt“ angezeigt. Behoben durch das Hinzufügen der Zeichenkette:
//+------------------------------------------------------------------+ //| Return the event status name | //+------------------------------------------------------------------+ string CEvent::StatusDescription(void) const { ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT); return ( status==EVENT_STATUS_MARKET_PENDING ? CMessage::Text(MSG_EVN_STATUS_MARKET_PENDING) : status==EVENT_STATUS_MARKET_POSITION ? CMessage::Text(MSG_EVN_STATUS_MARKET_POSITION) : status==EVENT_STATUS_HISTORY_PENDING ? CMessage::Text(MSG_EVN_STATUS_HISTORY_PENDING) : status==EVENT_STATUS_HISTORY_POSITION ? CMessage::Text(MSG_EVN_STATUS_HISTORY_POSITION) : status==EVENT_STATUS_BALANCE ? CMessage::Text(MSG_LIB_PROP_BALANCE) : status==EVENT_STATUS_MODIFY ? CMessage::Text(MSG_EVN_REASON_MODIFY) : CMessage::Text(MSG_EVN_STATUS_UNKNOWN) ); }
Beheben wir ein Versäumnis in der Klasse des Handelsobjekts: Die Politik des Füllens des Volumens (aus der Enumeration ENUM_ORDER_TYPE_FILLING ) wurde fälschlicherweise an die Positionseröffnungsmethode übergeben.
Implementieren wir die notwendigen Verbesserungen an den Handelsmethoden in MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh. Ich werde den Block zum Füllen der Handelsanforderungsstruktur in der Methode zur Eröffnung einer Position um die Einstellung der Volumenfüllungspolitik ergänzen:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = (ENUM_ORDER_TYPE)type; this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.type_filling= (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling); this.m_request.comment = (comment==NULL ? this.m_comment : comment); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::ResetLastError(); int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE); this.m_result.retcode=::GetLastError(); ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); if(ticket!=WRONG_VALUE) { this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); return true; } else { return false; } #endif }
In der Klasse wird der Wert für die Befüllungsrichtlinie zunächst auf die Variable m_type_filling gesetzt, wenn die Bibliothek mit den für Aufträge zulässigen Werten initialisiert wird (die Methode CEngine::TradingSetCorrectTypeFilling). Wird der Methode open ein negativer Wert für die Füllungsrichtlinie übergeben, wird der Wert aus der Variablen m_type_filling, die bei der Initialisierung der Bibliothek gesetzt wurde, verwendet. Wenn eine andere Art der Volumenfüllung angegeben werden soll, muss diese im Methodenparameter type_filling übergeben werden, und der übergebene Wert wird verwendet.
Zuvor gab es keine zusätzliche Zeichenfolge. Wenn eine andere (nicht standardmäßige) Richtlinie eingestellt werden sollte, war die Befüllungsrichtlinie immer Return (ORDER_FILLING_RETURN), da das Feld type_filling der Struktur MqlTradeRequest nicht gefüllt wurde und immer einen Nullwert hatte. Das Problem wurde jetzt behoben.
Beheben wir ähnliche Unzulänglichkeiten in anderen Methoden der Klasse, in denen das Volumen Füllung Politik ist in der einen oder anderen Weise benötigt:
//+------------------------------------------------------------------+ //| Close a position | //+------------------------------------------------------------------+ bool CTradeObj::ClosePosition(const ulong ticket, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- If the position selection failed. Write the error code and error description, send the message to the log and return 'false' if(!::PositionSelectByTicket(ticket)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); return false; } //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Get a position type and an order type inverse of the position type ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.type = type; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = ::PositionGetDouble(POSITION_VOLUME); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling= this.m_type_filling; //--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif } //+------------------------------------------------------------------+ //| Close a position partially | //+------------------------------------------------------------------+ bool CTradeObj::ClosePositionPartially(const ulong ticket, const double volume, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- If the position selection failed. Write the error code and error description, send the message to the log and return 'false' if(!::PositionSelectByTicket(ticket)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); return false; } //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Get a position type and an order type inverse of the position type ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Get a position volume double position_volume=::PositionGetDouble(POSITION_VOLUME); //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.position = ticket; this.m_request.symbol = this.m_symbol; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.type = type; this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = (volume<position_volume ? volume : position_volume); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling= this.m_type_filling; //--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif }
Nun wollen wir ähnliche Auslassungen in der Handelsklasse der Bibliothek in \MQL5\Include\DoEasy\Trading.mqh beheben.
In der Methode OpenPosition() wird die Handelsanforderungsstruktur mit Werten gefüllt, die auf den an die Methode übergebenen Werten basieren:
//--- Write the volume, deviation, comment and filling type to the request structure this.m_request.volume=volume; this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling());
und beim Aufruf der Methode zur Positionseröffnung eines Symbolhandelsobjekts geben seine Parameter nicht die Werte an, die in der Struktur des Handelsauftrags festgelegt sind, sondern diejenigen, die an die Positionseröffnungsmethode übergeben wurden:
//--- In the loop by the number of attempts for(int i=0;i<this.m_total_try;i++) { //--- Send a request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling); //... ... ...
Bringen wir das in Ordnung:
//--- Send a request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,this.m_request.comment,this.m_request.deviation,this.m_request.type_filling);
Um Ereignisse auf allen geöffneten Charts zu verfolgen und nicht nur auf demjenigen, mit dem das auf der Bibliothek basierende Programm verbunden ist, wird ein Indikator auf jedem geöffneten (oder bereits geöffneten) Chart platziert, der Chart-Ereignisse verfolgt und an das Programm sendet.
Wenn das Client-Terminal zunächst mit einem Server verbunden war und die Charts verschiedener Instrumente geöffnet wurden und das Terminal dann mit einem anderen Server verbunden wurde, auf dem es keine Instrumente gibt, deren Charts bereits geöffnet sind, kann das Programm diese Indikatoren nicht auf solchen Charts platzieren, und das Journal enthält Fehlereinträge, die auf einen Fehler bei der Erstellung des Indikators hinweisen und nicht auf das Fehlen von Symbolen für offene Charts auf dem Server. Um diese zweideutigen Protokolleinträge zu beheben, müssen wir eine Prüfung auf das Vorhandensein des Symbols auf dem Server hinzufügen, bevor wir den Indikator erstellen.
Fügen wir in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in der Methode zur Erstellung des Ereignissteuerungsindikators, eine Prüfung auf das Vorhandensein eines Symbols auf dem Server hinzu:
//+------------------------------------------------------------------+ //| CChartObjectsControl: Create the event control indicator | //+------------------------------------------------------------------+ bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main) { //--- If the symbol is not on the server, return 'false' bool is_custom=false; if(!::SymbolExist(this.Symbol(), is_custom)) { CMessage::ToLog(DFUN+" "+this.Symbol()+": ",MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER); return false; } //--- Create the indicator this.m_chart_id_main=chart_id_main; string name="::"+PATH_TO_EVENT_CTRL_IND; ::ResetLastError(); this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main); if(this.m_handle_ind==INVALID_HANDLE) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main; ::Print ( DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ", CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\"" ); return true; }
Wenn nun kein Symbol auf dem Server vorhanden ist, wird im Journal eine entsprechende Meldung angezeigt, und die Methode gibt false zurück.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObjGBaseObj.mqh des grafischen Basisobjekts, und zwar in der Methode, die die Beschreibung des Typs des grafischen Elements zurückgibt, fügen wir den fehlenden Typ für ein grafisches Objekt hinzu - Bitmap:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_BITMAP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_BITMAP) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP) : type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR) : //--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR) : type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB) : "Unknown" ); }
Wir werden auch die Musterklassen leicht überarbeiten - ihre Organisation und Struktur. Die abstrakte Basismusterklasse befindet sich in \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, und dann werden die von der Basisklasse geerbten Musterklassen in diese Klasse geschrieben.
Lassen Sie uns alle Musterklassen in eigene Dateien aufteilen. Jetzt ist es möglich, die Mustersymbole als reguläre Punkte im Chart anzuzeigen, die mit Hilfe von standardmäßigen Trendlinienobjekten mit Preis- und Zeitkoordinaten auf einem Balken gezeichnet werden. Ich habe beschlossen, diese Funktion aufzugeben, um den Code zu vereinfachen. Alle Symbole werden als grafische Objekte, als Bitmap gezeichnet. Zeichnungen von Mustern werden auf der Grundlage der Größen der Balken, aus denen das Muster besteht, erstellt. Um die Größe von Zeichenobjekten zu ändern, wenn der horizontale Maßstab des Charts geändert wird, müssen wir eine Variable eingeben, die den Maßstabswert speichert. Die Variablen für die Speicherung der übrigen Chartgrößen sind bereits im Grundmusterobjekt enthalten. Wenn Sie die Größe des Charts ändern, werden die neuen Werte in alle erstellten Objekte der gefundenen Muster passen, und sie werden entsprechend der neuen Größen neu gezeichnet werden.
Wir erstellen in der Bibliotheksdatei \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ zwei neue Dateien PatternPinBar.mqh und PatternInsideBar.mqh. Dort platzieren wir die Klassen der Muster „Pin Bar“ und „Inside Bar“ (die direkt in der abstrakten Basismusterklassendatei festgelegt wurden) mit Hilfe von Ausschneiden und Einfügen. Als Nächstes werden wir Änderungen an ihnen vornehmen, aber zunächst werden wir mit der Bearbeitung der abstrakten Musterklasse fortfahren.
Aus dem geschützten Abschnitt der Klasse entfernen wir die Flag-Variable m_draw_dots, die angibt, wie die Mustersymbole mit Punkten gezeichnet werden, und deklarieren die Variable zum Speichern der Chartbreite in Pixeln:
protected: CForm *m_form; // Pointer to form object CGCnvBitmap *m_bitmap; // Pointer to the bitmap object int m_digits; // Symbol's digits value ulong m_symbol_code; // Symbol as a number (sum of name symbol codes) string m_name_graph_obj; // Name of the graphical object displaying the pattern double m_price; // Price level the graphical object is placed at color m_color_bullish; // Color of a graphical object set to the bullish pattern icon color m_color_bearish; // Color of a graphical object set to the bearish pattern icon color m_color_bidirect; // Color of a graphical object set to the bidirectional pattern icon color m_color; // Graphical object color color m_color_panel_bullish; // Bullish pattern panel color color m_color_panel_bearish; // Bearish pattern panel color color m_color_panel_bidirect; // Bidirectional pattern panel color int m_bars_formation; // Number of bars in the formation (nested pattern) bool m_draw_dots; // Draw on the chart with dots int m_chart_scale; // Chart scale int m_chart_height_px; // Height of the chart in pixels int m_chart_width_px; // Height of the chart in pixels double m_chart_price_max; // Chart maximum double m_chart_price_min; // Chart minimum public:
Die Methoden zur Berechnung der Breite und Höhe eines Bitmap-Objekts
//--- Calculate the bitmap object (1) width and (2) height int GetBitmapWidth(void); int GetBitmapHeight(void);
und benennen wir sie in die richtigen Namen um und deklarieren sie als virtuell:
//--- Calculate the bitmap object (1) width and (2) height virtual int CalculatetBitmapWidth(void); virtual int CalculatetBitmapHeight(void);
„Get“ bedeutet jedoch „Abrufen“, nicht „Berechnen“. Virtuelle Methoden ermöglichen es geerbten Klassen, ihre eigenen Berechnungen der Breite und Höhe des Musters zu verwenden, abhängig von der Art des Musters und der Methode, es zu zeichnen.
Im öffentlichen Abschnitt der Klasse entfernen wird die Methode SetDrawAsDots():
public: //--- Remove a graphical object bool DeleteGraphObj(bool redraw=false); //--- Set graphical object display colors and pattern display color void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Set the flag for drawing pattern labels as dots void SetDrawAsDots(const bool flag) { this.m_draw_dots=flag; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel void SetColorPanelBullish(const color clr) { this.m_color_panel_bullish=clr; } void SetColorPanelBearish(const color clr) { this.m_color_panel_bearish=clr; } void SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components void SetColorPanelBullish(const uchar R,const uchar G,const uchar B); void SetColorPanelBearish(const uchar R,const uchar G,const uchar B); void SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B); //--- Draw the pattern icon on the chart virtual void Draw(const bool redraw); //--- (1) Display, (2) hide the pattern icon on the chart void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Display and (2) hide the info panel on the chart void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
und deklarieren die virtuelle Methode Redraw():
public: //--- Remove a graphical object bool DeleteGraphObj(bool redraw=false); //--- Set graphical object display colors and pattern display color void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel void SetColorPanelBullish(const color clr) { this.m_color_panel_bullish=clr; } void SetColorPanelBearish(const color clr) { this.m_color_panel_bearish=clr; } void SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components void SetColorPanelBullish(const uchar R,const uchar G,const uchar B); void SetColorPanelBearish(const uchar R,const uchar G,const uchar B); void SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B); //--- (1) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw) { return true; } //--- (1) Display, (2) hide the pattern icon on the chart void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Display and (2) hide the info panel on the chart void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
Mit der Methode Redraw() wird das Bitmap-Objekt mit den neuen Abmessungen neu gezeichnet. Da jeder Mustertyp seine eigenen Arten von Bitmaps haben kann, wird die Methode als virtuell deklariert und gibt hier einfach true zurück. In geerbten Klassen wird die Methode überschrieben, um genau die Bitmap neu zu zeichnen, die für das angegebene Muster gezeichnet wird.
Dort, im öffentlichen Abschnitt, werden wir auch die Methoden zum Setzen und Zurückgeben der Chartbreite in Pixeln festlegen:
//--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low void SetChartScale(const int scale) { this.m_chart_scale=scale; } void SetChartHeightInPixels(const int height) { this.m_chart_height_px=height; } void SetChartWidthInPixels(const int width) { this.m_chart_width_px=width; } void SetChartPriceMax(const double price) { this.m_chart_price_max=price; } void SetChartPriceMin(const double price) { this.m_chart_price_min=price; } //--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low int ChartScale(void) const { return this.m_chart_scale; } int ChartHeightInPixels(void) const { return this.m_chart_height_px; } int ChartWidthInPixels(void) const { return this.m_chart_width_px; } double ChartPriceMax(void) const { return this.m_chart_price_max; } double ChartPriceMin(void) const { return this.m_chart_price_min; }
Wenn die Breite des Charts geändert wird, schreibt die Musterverwaltungsklasse die neuen Chartgrößen nacheinander in alle Musterobjekte - sodass Sie in jedem der erstellten Musterobjekte nicht die Eigenschaften des Charts erhalten, sondern die neuen Größen nur einmal erhalten, wenn sie sich ändern, und sie dann in alle erstellten Musterobjekte schreiben.
Ganz am Ende des Klassenkonstruktors entfernen wir die Zeichenfolge für die Initialisierung einer nicht mehr benötigten Variablen:
//--- Set base colors of the pattern information panels this.m_color_panel_bullish=clrLightGray; this.m_color_panel_bearish=clrLightGray; this.m_color_panel_bidirect=clrLightGray; this.m_form=NULL; this.m_bitmap=NULL; this.m_draw_dots=true; this.m_bars_formation=1; }
Wir fügen in der Methode, die die Beschreibung der realen Eigenschaft des Musters zurückgibt, zwei Codeblöcke für die Anzeige der Beschreibungen von zwei neuen Mustereigenschaften hinzu:
//+------------------------------------------------------------------+ //| Return the description of the pattern real property | //+------------------------------------------------------------------+ string CPattern::GetPropertyDescription(ENUM_PATTERN_PROP_DOUBLE property) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( property==PATTERN_PROP_BAR_PRICE_OPEN ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_HIGH ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_HIGH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_LOW ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_LOW)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_CLOSE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_CANDLE_SIZES ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : "" ); }
In der Methode, die das Infopanel auf dem Chart anzeigt, müssen die Charteigenschaften nicht mehr abgerufen werden, da sie bereits im Musterobjekt festgelegt sind, wenn es erstellt wird oder wenn die Größe des Charts geändert wird.
Entfernen wir die Zeichenketten für den Empfang von Charteigenschaften aus der Methode:
//+------------------------------------------------------------------+ //| Display the info panel on the chart | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- If there is no panel object yet, create it if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Get the chart width and height int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); //--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart
Die folgenden, bei der Erstellung des Objekts voreingestellten Eigenschaften werden nun anstelle der zuvor erhaltenen Charteigenschaften verwendet:
//+------------------------------------------------------------------+ //| Display the info panel on the chart | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- If there is no panel object yet, create it if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart int cx=(x+this.m_form.Width() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : x); int cy=(y+this.m_form.Height()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : y); //--- Set the calculated coordinates and display the panel if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy)) this.m_form.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Die Strings, die sich auf das Zeichnen der Mustersymbole mit Punkten beziehen entfernen wir, aus der Methode zum Zeichnen, Anzeigen und Ausblenden von Mustersymbolen:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Draw(const bool redraw) { //--- If the graphical object has not yet been created, create it if(::ObjectFind(this.m_chart_id,this.m_name_graph_obj)<0) this.CreateTrendLine(this.m_chart_id,this.m_name_graph_obj,0,this.Time(),this.m_price,this.Time(),this.m_price,this.m_color,5); //--- Otherwise - display else this.Show(redraw); } //+------------------------------------------------------------------+ //| Display the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Show(const bool redraw=false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); return; } if(this.m_bitmap!=NULL) this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Hide the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Hide(const bool redraw=false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS); return; } if(this.m_bitmap!=NULL) this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Jetzt sehen diese Methoden einfacher aus:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPattern::Draw(const bool redraw) { this.Show(redraw); return true; } //+------------------------------------------------------------------+ //| Display the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Show(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Hide the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Hide(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Als Nächstes werden wir die Musterklassen verfeinern, deren Codes aus der abstrakten Musterdatei übernommen wurden.
Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh der Klasse für das Muster der „Pin Bars“ und nehmen die Änderungen vor.
Zuvor wurden die Symbole dieses Musters nur als Punkte mit Hilfe von Standard-Grafikobjekten gezeichnet. Nun müssen wir Methoden zum Zeichnen von Punkten auf dem grafischen Bitmap-Objekt hinzufügen.
Fügen wir dem Hauptteil der Klasse die Deklaration der neuen Methoden hinzu:
//+------------------------------------------------------------------+ //| PatternPinBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| Pin Bar pattern class | //+------------------------------------------------------------------+ class CPatternPinBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Calculate the bitmap object (1) width and (2) height virtual int CalculatetBitmapWidth(void) { return(20); } virtual int CalculatetBitmapHeight(void) { return(40); } public: //--- Return the flag of the pattern supporting the specified property virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Return description of the pattern (1) status and (2) type virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_PIN_BAR); } //--- Draw the pattern icon on the chart virtual bool Draw(const bool redraw); //--- Constructor CPatternPinBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Die virtuellen Methoden CalculatetBitmapWidth() und CalculatetBitmapHeight() geben immer die streng spezifizierten Abmessungen des Bildes 20x40 Pixel zurück, da für dieses Muster, das nur auf einem Balken gezeichnet wird, weder die Höhe noch die Breite des Bildes berechnet werden muss - es muss immer die gleiche Größe haben. Der Ankerpunkt der Bitmap wird in der Mitte des Objekts gesetzt, und die Punkte werden immer in der oberen oder unteren Hälfte der Bitmap auf die Leinwand gezeichnet, je nach Richtung des Musters. Bei einem Aufwärts-Muster wird der Punkt in der unteren Hälfte der Bitmap gezeichnet, bei einem Abwärts-Muster in der oberen Hälfte. Auf diese Weise können die Musterpunkte unabhängig von der vertikalen Skala und der Chartperiode immer im gleichen Abstand zum Docht der Kerzen angezeigt werden, was sehr bequem und praktisch ist.
In der Implementierung der Methode, die das Erscheinungsbild des Info-Panels CreateInfoPanelView() erstellt, entfernen wir die Zeichenketten zum Abrufen der Charteigenschaften:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());
Jetzt werden diese Eigenschaften bei der Erstellung eines Objekts voreingestellt oder aktualisiert, wenn die Größe des Charts geändert wird. Daher verwenden wir jetzt die Werte aus den Variablen, die die Breite und Höhe des Charts enthalten:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());
Implementieren wir nun die Methoden zum Zeichnen des Mustersymbols.
Die Methode, die das Mustersymbol auf dem Chart zeichnet:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternPinBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Wenn es noch kein physisches Bitmap-Objekt gibt, erstellen wir es und zeigen es dann im Chart an.
Die Methode, die das Bitmap-Objekt erstellt:
//+------------------------------------------------------------------+ //| Create the bitmap object | //+------------------------------------------------------------------+ bool CPatternPinBar::CreateBitmap(void) { //--- If the bitmap object has already been created earlier, return 'true' if(this.m_bitmap!=NULL) return true; //--- Calculate the object coordinates and dimensions datetime time=this.MotherBarTime(); double price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh()); int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Create the Bitmap object this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect); if(this.m_bitmap==NULL) return false; //--- Set the object origin to its center and remove the tooltip ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Draw the bitmap object appearance this.CreateBitmapView(); return true; }
Die Logik der Methode ist im Code kommentiert. Berücksichtigen wir noch bei der Festlegung der Objektkoordinaten die Musterrichtung. Ist das Muster aufwärts gerichtet, ist die Koordinate das Tief des Balkens mit dem Muster, ist es abwärts, ist die Koordinate das Hoch.
Die Methode zur Erstellung für das Aussehen des Bitmap-Objekts:
//+------------------------------------------------------------------+ //| Create the bitmap object appearance | //+------------------------------------------------------------------+ void CPatternPinBar::CreateBitmapView(void) { this.m_bitmap.Erase(CLR_CANV_NULL,0); int y=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_bitmap.Height()/2+6 : this.m_bitmap.Height()/2-6); int x=this.m_bitmap.Width()/2; int r=2; this.m_bitmap.DrawCircleFill(x,y,r,this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish); this.m_bitmap.Update(false); }
Leeren wir zunächst den Leinwand mit einer vollständig transparenten Farbe. Dann bestimmen wir die lokalen Koordinaten des zu zeichnenden Punktes. Bei einem Aufwärts-Muster ist die Y-Koordinate die halbe Höhe des Musters plus 6 Pixel (6 Pixel unterhalb der Mitte des Musters). Für das Abwärts-Muster ziehen Sie 6 Pixel von den Koordinaten der Bitmap-Mitte ab. Die X-Koordinate entspricht der halben Breite der Bitmap. Der Radius des gezeichneten Kreises ist auf 2 eingestellt, sodass der Punkt normalerweise auf jedem Chart-Zeitrahmen sichtbar ist.
Nun wollen wir ähnliche Verbesserungen an der Datei \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh mit dem Muster der „Inside Bar“ vornehmen.
Deklarieren wir die virtuelle Methode zum erneuten Zeichnen des Mustersymbols:
//+------------------------------------------------------------------+ //| PatternInsideBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| "Inside Bar" pattern class | //+------------------------------------------------------------------+ class CPatternInsideBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); public: //--- Return the flag of the pattern supporting the specified property virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Return description of the pattern (1) status and (2) type virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_INSIDE_BAR); } //--- (1) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Constructor CPatternInsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Die Methode zum Neuzeichnen des Mustersymbols mit neuen Größen implementieren wir außerhalb des Klassenkörpers:
//+------------------------------------------------------------------+ //| Redraw the pattern icon on the chart with a new size | //+------------------------------------------------------------------+ bool CPatternInsideBar::Redraw(const bool redraw) { //--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) return CPatternInsideBar::Draw(redraw); //--- Calculate the new object dimensions int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- If canvas resizing failed, return 'false' if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); //--- display and return 'true' this.Show(redraw); return true; }
Die Logik der Methode ist im Code kommentiert - hier ist alles sehr einfach: Wir berechnen die neuen Abmessungen der Leinwand, ändern ihre Abmessungen und zeichnen eine neue Bitmap auf der Leinwand entsprechend den neuen Abmessungen.
In der Methode, mit der das Erscheinungsbild des Infofelds erstellt wird, gab es einen Fehler bei der Berechnung der Mustergröße in Balken:
//--- Create strings to describe the pattern, its parameters and search criteria string name=::StringFormat("Inside Bar (%lu bars)",int(this.Time()-this.MotherBarTime())/::PeriodSeconds(this.Timeframe())+1); string param=this.DirectDescription();
Diese Berechnung hat ihre Berechtigung, aber nur, wenn benachbarte Balken des Musters nicht durch Wochenenden getrennt sind. Wenn zwischen dem linken und dem rechten Balken des Musters Wochenenden liegen, werden diese zur Anzahl der Balken addiert, und statt 2 erhalten wir den Wert 4.
Lassen Sie uns die Berechnung korrigieren:
//--- Create strings to describe the pattern, its parameters and search criteria string name=::StringFormat("Inside Bar (%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time())); string param=this.DirectDescription();
Jetzt erhalten wir immer die richtige Anzahl von Balken zwischen zwei Balken des Musters.
Da die Werte für die Chartbreite und -höhe jetzt im Voraus in Variablen festgelegt werden, entfernen wir den Empfang der Charteigenschaften:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());
und passen die Berechnung der Tafelkoordinaten so an, dass voreingestellte Werte aus den Variablen verwendet werden:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());
Wir entfernen den Codeblock zum Zeichnen von Punkten in der Methode zum Zeichnen eines Mustersymbols in einem Chart:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ void CPatternInsideBar::Draw(const bool redraw) { //--- If the flag for drawing with dots is set, call the parent class method and leave if(this.m_draw_dots) { CPattern::Draw(redraw); return; } //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return; } //--- display this.Show(redraw); }
Jetzt sieht die Methode einfacher aus:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternInsideBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Alle anderen Verbesserungen werden nach der Erstellung der Klasse des neuen Musters „Outside Bar“ implementiert.
Musterklasse „Outside Bar“
Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Series\Patterns\ erstellen wir die neue Datei PatternOutsideBar.mqh mit der Klasse CPatternOutsideBar.
Die Klasse sollte von der Basisklasse des Musterobjekts geerbt werden, und ihre Datei sollte in die erstellte Musterklassendatei aufgenommen werden:
//+------------------------------------------------------------------+ //| PatternOutsideBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| "Outside bar" pattern class | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { }
Deklarieren wir die Standardmethoden für Musterobjektklassen:
//+------------------------------------------------------------------+ //| "Outside bar" pattern class | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Calculate the bitmap object height virtual int CalculatetBitmapHeight(void); public: //--- Return the flag of the pattern supporting the specified property virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Return description of the pattern (1) status and (2) type virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_OUTSIDE_BAR);} //--- (1) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Constructor CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Alle diese Methoden sind für alle Muster gleich. Ihre Umsetzung unterscheidet sich jedoch leicht von Muster zu Muster. Schauen wir uns die einzelnen Methoden an.
Im Konstruktor der Klasse definieren wir den Namen des Musters, die Anzahl der Kerzen, die in der Form enthalten sind, und die Anzahl der benachbarten, aufeinanderfolgenden Muster, die der Anzahl der Kerzen des Musters entspricht:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPatternOutsideBar::CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct) : CPattern(PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,id,direct,symbol,timeframe,rates) { this.SetProperty(PATTERN_PROP_NAME,"Outside Bar"); this.SetProperty(PATTERN_PROP_CANDLES,2); this.m_bars_formation=(int)this.GetProperty(PATTERN_PROP_CANDLES); }
Die Methode, die das Erscheinungsbild des Info-Panels erzeugt:
//+------------------------------------------------------------------+ //| Create the info panel appearance | //+------------------------------------------------------------------+ void CPatternOutsideBar::CreateInfoPanelView(void) { //--- If the form object is not created, leave if(this.m_form==NULL) return; //--- Change the color tone for bullish and bearish patterns: the bullish ones will have a blue tint, while the bearish ones will have a red tint color color_bullish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bullish,5),0,0,100); color color_bearish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bearish,5),100,0,0); color color_bidirect=this.m_color_panel_bidirect; //--- Declare the array for the initial and final colors of the gradient fill color clr[2]={}; //--- Depending on the direction of the pattern, change the lightness of the corresponding colors - the initial one is a little darker, the final one is a little lighter switch(this.Direction()) { case PATTERN_DIRECTION_BULLISH : clr[0]=this.m_form.ChangeColorLightness(color_bullish,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bullish,2.5); break; case PATTERN_DIRECTION_BEARISH : clr[0]=this.m_form.ChangeColorLightness(color_bearish,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bearish,2.5); break; default: clr[0]=this.m_form.ChangeColorLightness(color_bidirect,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bidirect,2.5); break; } //--- Set the background and form frame colors this.m_form.SetBackgroundColor(this.Direction()==PATTERN_DIRECTION_BULLISH ? color_bullish : this.Direction()==PATTERN_DIRECTION_BEARISH ? color_bearish : color_bidirect,true); this.m_form.SetBorderColor(clrGray,true); //--- Create strings to describe the pattern, its parameters and search criteria string name=::StringFormat("Outside Bar (%.2f/%.2f)",this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE)); string param=::StringFormat("%s (%.2f/%.2f)",this.DirectDescription(),this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION)); //--- Set the coordinates of the panel and calculate its width and height depending on the size of the texts placed on the panel int x=3; int y=20; int w=4+(::fmax(20+this.m_form.TextWidth(name),::fmax(x+this.m_form.TextWidth(param),x+this.m_form.TextWidth(::TimeToString(this.Time()))))); int h=2+(20+this.m_form.TextHeight(this.DirectDescription())+this.m_form.TextHeight(::TimeToString(this.Time()))); //--- Set the width and height of the panel according to the calculated values this.m_form.SetWidth(w); this.m_form.SetHeight(h); //--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY()); this.m_form.SetCoordX(cx); this.m_form.SetCoordY(cy); //--- Fill the background with a gradient color this.m_form.Erase(clr,200,true,false); //--- Draw the panel frame, an icon with (i), draw the header text with the proportions of a candle and separate the header with a horizontal line this.m_form.DrawFrameSimple(0,0,this.m_form.Width(),this.m_form.Height(),1,1,1,1,this.m_form.BorderColor(),200); this.m_form.DrawIconInfo(1,1,200); this.m_form.Text(20,3,name,clrBlack,200); this.m_form.DrawLine(1,18,this.m_form.Width()-1,18,clrDarkGray,250); //--- Under the horizontal line, enter the pattern description with its search criteria and the date of the pattern-defining bar y=20; this.m_form.Text(x,y,param,clrBlack,200); y+=this.m_form.TextHeight(::TimeToString(this.Time())); this.m_form.Text(x,y,::TimeToString(this.Time()),clrBlack,200); //--- Update the panel while redrawing the chart this.m_form.Update(true); }
Die Logik der Methode ist im Code kommentiert. Hier wird ein Feld mit einer Verlaufsfüllung gezeichnet, die bei einem Abwärts-Muster rot und bei einem Aufwärts-Muster blau gefärbt ist. Oben wird ein Symbol mit dem Zeichen i gezeichnet und der Name des Musters mit seinen Merkmalen geschrieben - das Verhältnis von zwei Balken des Musters. Der untere Teil beschreibt die Richtung des Musters mit den angegebenen Werten für die Mustersuche und die Zeit des gefundenen Musters.
Die Methode, die das Mustersymbol auf dem Chart zeichnet:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Die Methode, mit der das Mustersymbol auf der Karte mit einer neuen Größe neu gezeichnet wird:
//+------------------------------------------------------------------+ //| Redraw the pattern icon on the chart with a new size | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Redraw(const bool redraw) { //--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) return CPatternOutsideBar::Draw(redraw); //--- Calculate the new object dimensions int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- If canvas resizing failed, return 'false' if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); //--- display and return 'true' this.Show(redraw); return true; }
Wir ändern einfach die Größe der Leinwand und zeichnen ein neues Muster-Bitmap in der neuen Größe.
Die Methode, die das Bitmap-Objekt erstellt:
//+------------------------------------------------------------------+ //| Create the bitmap object | //+------------------------------------------------------------------+ bool CPatternOutsideBar::CreateBitmap(void) { //--- If the bitmap object has already been created earlier, return 'true' if(this.m_bitmap!=NULL) return true; //--- Calculate the object coordinates and dimensions datetime time=this.MotherBarTime(); double price=(this.BarPriceHigh()+this.BarPriceLow())/2; int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Create the Bitmap object this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect); if(this.m_bitmap==NULL) return false; //--- Set the object origin to its center and remove the tooltip ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Draw the bitmap object appearance this.CreateBitmapView(); return true; }
Wenn das grafische Objekt bereits zuvor erstellt wurde, wird die Methode einfach verlassen. Andernfalls erhalten wir den Preis und die Uhrzeit, zu der das grafische Objekt platziert werden soll, berechnen seine Breite und Höhe und erstellen ein neues Objekt. Dann setzen wir den Ankerpunkt in die Mitte des Objekts und zeichnen sein Aussehen darauf. Der Kurs, auf dem der zentrale Punkt des grafischen Objekts stehen wird, wird auf der Grundlage des Mittelpunkts der größten Kerze des Musters berechnet.
Die Methode, die das Aussehen eines Bitmap-Objekts erzeugt:
//+------------------------------------------------------------------+ //| Create the bitmap object appearance | //+------------------------------------------------------------------+ void CPatternOutsideBar::CreateBitmapView(void) { this.m_bitmap.Erase(CLR_CANV_NULL,0); int x=this.m_bitmap.Width()/2-int(1<<this.m_chart_scale)/2; this.m_bitmap.DrawRectangleFill(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish),80); this.m_bitmap.DrawRectangle(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,clrGray); this.m_bitmap.Update(false); }
Hier wird die Bitmap zunächst mit einer vollständig transparenten Farbe gelöscht. Dann wird die lokale X-Koordinate des Beginns des gemalten Bereichs berechnet, abhängig vom Maßstab der Karte. Ein gefülltes Rechteck wird über die gesamte Höhe des grafischen Objekts mit der berechneten anfänglichen X-Koordinate gezeichnet, und darüber wird ein rechteckiger Rahmen mit den gleichen Koordinaten und der gleichen Größe gezeichnet.
Die Methode zur Berechnung der Höhe eines Zeichnungsobjekts:
//+------------------------------------------------------------------+ //| Calculate the bitmap object height | //+------------------------------------------------------------------+ int CPatternOutsideBar::CalculatetBitmapHeight(void) { //--- Calculate the chart price range and pattern price range double chart_price_range=this.m_chart_price_max-this.m_chart_price_min; double patt_price_range=this.BarPriceHigh()-this.BarPriceLow(); //--- Using the calculated price ranges, calculate and return the height of the bitmap object return (int)ceil(patt_price_range*this.m_chart_height_px/chart_price_range)+8; }
Hier erhalten wir die Preisspanne des Charts - vom Hoch auf dem Chart bis zum Tief auf dem Chart - und die Preisspanne des Musters - vom Hoch der definierenden Kerze bis zum Tief der definierenden Kerze. Dann berechnen wir das Verhältnis eines Bereichs zu einem anderen in Pixeln und geben die resultierende Höhe des Bitmap-Objekts zurück, indem wir 8 Pixel dazu addieren - vier oben und vier unten.
Die Musterklasse „Outside Bar“ ist fertig. Jetzt müssen wir eine Menge identischer Methoden in den Zeitreihenklassen loswerden, von denen jede die gleiche Aufgabe für ihr eigenes Muster erfüllt. Wir machen nur eine Methode für jede Aktion auf dem Muster, in der wir den Typ des gewünschten Musters angeben.
Wir werden alle erforderlichen Parameter der im Muster enthaltenen Beziehungen der Kerzen mit Hilfe der Struktur MqlParam an die Musterklassen übergeben. Dadurch können wir völlig unterschiedliche Parameter an verschiedene Klassen mit unterschiedlichen Mustern übergeben, und nicht nur solche, die streng durch formale Variablen definiert sind, die für alle Klassen gleich sind.
Wir werden die Namen dieser Variablen, die die verschiedenen Verhältnisse der Musterkerzen angeben, vorerst nicht ändern. Aber wenn nötig, werden wir sie in „gesichtslose“ Variablen umbenennen, wie „param1“, „param2“ usw. In den Klassenkonstruktoren werden wir jeder dieser Variablen gegebenenfalls den erforderlichen Musterparameter zuweisen. Für den Moment werden wir uns mit den bereits benannten Variablen begnügen, die für alle Muster gleich sind.
Ersetzen wir in der Objektklasse der Balken in \MQL5\Include\DoEasy\Objects\Series\Bar.mqh den Methodennamen AddPattern() durch AddPatternType():
//--- Return itself CBar *GetObject(void) { return &this;} //--- Set (1) bar symbol, timeframe and time, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetProperties(const MqlRates &rates); //--- Add the pattern type on bar void AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type; } //--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property)
Dennoch fügt diese Methode dem Balkenobjekt den Mustertyp hinzu und nicht einen Zeiger auf das Muster. Daher ist es logischer, den Namen der Methode in einen korrekteren Namen zu ändern.
Um die gewünschten Muster aus den Listen auswählen und empfangen zu können, binden wir in der Datei \MQL5\Include\DoEasy\Services\Select.mqh der Klasse CSelect alle Musterdateien ein:
//+------------------------------------------------------------------+ //| Select.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 <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\\Patterns\PatternPinBar.mqh" #include "..\Objects\Series\\Patterns\PatternInsideBar.mqh" #include "..\Objects\Series\\Patterns\PatternOutsideBar.mqh" #include "..\Objects\Series\SeriesDE.mqh" #include "..\Objects\Indicators\Buffer.mqh" #include "..\Objects\Indicators\IndicatorDE.mqh" #include "..\Objects\Indicators\DataInd.mqh" #include "..\Objects\Ticks\DataTick.mqh" #include "..\Objects\Book\MarketBookOrd.mqh" #include "..\Objects\MQLSignalBase\MQLSignal.mqh" #include "..\Objects\Chart\ChartObj.mqh" #include "..\Objects\Graph\GCnvElement.mqh" #include "..\Objects\Graph\Standard\GStdGraphObj.mqh"
Dadurch werden Musterklassen in Zeitreihenklassen für die Handhabung von Mustern verfügbar.
Wir müssen Arrays der MqlParam-Strukturen auf Gleichheit vergleichen. Wir schreiben Funktionen zum Vergleich von Strukturfeldern und Strukturarrays in \MQL5\Include\DoEasy\Services\DELib.mqh:
//+------------------------------------------------------------------+ //| Compare MqlParam structures with each other | //+------------------------------------------------------------------+ bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) { if(struct1.type!=struct2.type) return false; switch(struct1.type) { //--- integer types case TYPE_BOOL : case TYPE_CHAR : case TYPE_UCHAR : case TYPE_SHORT : case TYPE_USHORT : case TYPE_COLOR : case TYPE_INT : case TYPE_UINT : case TYPE_DATETIME : case TYPE_LONG : case TYPE_ULONG : return(struct1.integer_value==struct2.integer_value); //--- real types case TYPE_FLOAT : case TYPE_DOUBLE : return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==0); //--- string type case TYPE_STRING : return(struct1.string_value==struct2.string_value); default : return false; } } //+------------------------------------------------------------------+ //| Compare array of MqlParam structures with each other | //+------------------------------------------------------------------+ bool IsEqualMqlParamArrays(MqlParam &array1[],MqlParam &array2[]) { int total=ArraySize(array1); int size=ArraySize(array2); if(total!=size || total==0 || size==0) return false; for(int i=0;i<total;i++) { if(!IsEqualMqlParams(array1[i],array2[i])) return false; } return true; } //+------------------------------------------------------------------+
Die Klassen für die Musterverwaltung befinden sich in der Klassendatei \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh timeseries. Entfernen wir in der abstrakten Musterverwaltungsklasse die nicht mehr benötigten Variablen und fügen neue hinzu:
//+------------------------------------------------------------------+ //| Abstract pattern control class | //+------------------------------------------------------------------+ class CPatternControl : public CBaseObjExt { private: ENUM_TIMEFRAMES m_timeframe; // Pattern timeseries chart period string m_symbol; // Pattern timeseries symbol double m_point; // Symbol Point bool m_used; // Pattern use flag bool m_drawing; // Flag for drawing the pattern icon on the chart bool m_draw_dots; // Flag for drawing the pattern icon on the chart with dots //--- Handled pattern ENUM_PATTERN_TYPE m_type_pattern; // Pattern type protected: //--- Candle proportions double m_ratio_body_to_candle_size; // Percentage ratio of the candle body to the full size of the candle double m_ratio_larger_shadow_to_candle_size; // Percentage ratio of the size of the larger shadow to the size of the candle double m_ratio_smaller_shadow_to_candle_size; // Percentage ratio of the size of the smaller shadow to the size of the candle double m_ratio_candle_sizes; // Percentage of candle sizes uint m_min_body_size; // The minimum size of the candlestick body ulong m_object_id; // Unique object code based on pattern search criteria //--- List views CArrayObj *m_list_series; // Pointer to the timeseries list CArrayObj *m_list_all_patterns; // Pointer to the list of all patterns CPattern m_pattern_instance; // Pattern object for searching by property //--- Graph ulong m_symbol_code; // Chart symbol name as a number int m_chart_scale; // Chart scale int m_chart_height_px; // Height of the chart in pixels int m_chart_width_px; // Height of the chart in pixels double m_chart_price_max; // Chart maximum double m_chart_price_min; // Chart minimum
Zuvor wurde bei der Mustersuche die minimale Kerzengröße der Variable für die Suche nach einem Muster zugewiesen:
virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const { return WRONG_VALUE; }
Diese Größe wird nun über die Variable MqlParam übertragen, sodass diese Variable nun in allen Mustermanagementklassen aus allen Mustersuchmethoden entfernt wird:
//--- (1) Search for a pattern, return direction (or -1 if no pattern is found), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code, //--- (4) return the list of patterns managed by the object virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { return WRONG_VALUE; } virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar){ return NULL; } virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { return 0; } virtual CArrayObj*GetListPatterns(void) { return NULL; }
Entfernen wir aus dem öffentlichen Abschnitt der Klasse die Methoden zum Setzen und Abrufen von Eigenschaften der Punktmusterzeichnung:
//--- (1) Set and (2) return the flag for drawing pattern icons as dots
void SetDrawingAsDots(const bool flag,const bool redraw);
bool IsDrawingAsDots(void) const { return this.m_draw_dots; }
Deklarieren wir das Array der Muster-Parameter, fügen die Methoden für die Behandlung neuer Variablen hinzu, entfernen in der virtuellen Methode CreateAndRefreshPatternList() die Übergabe des Parameters min_body_size, und fügen im parametrischen Konstruktor die Übergabe des Arrays der Pattern-Eigenschaften über das Array der MqlParam-Struktur hinzu. Wir deklarieren eine neue Methode, um alle vorhandenen Muster im Chart neu zu zeichnen:
public: MqlParam PatternParams[]; // Array of pattern parameters //--- Return itself CPatternControl *GetObject(void) { return &this; } //--- (1) Set and (2) return the pattern usage flag void SetUsed(const bool flag) { this.m_used=flag; } bool IsUsed(void) const { return this.m_used; } //--- (1) Set and (2) return the pattern drawing flag void SetDrawing(const bool flag) { this.m_drawing=flag; } bool IsDrawing(void) const { return this.m_drawing; } //--- Set the necessary percentage ratio of the candle body to the full size of the candle, //--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body void SetRatioBodyToCandleSizeValue(const double value) { this.m_ratio_body_to_candle_size=value; } void SetRatioLargerShadowToCandleSizeValue(const double value) { this.m_ratio_larger_shadow_to_candle_size=value; } void SetRatioSmallerShadowToCandleSizeValue(const double value) { this.m_ratio_smaller_shadow_to_candle_size=value; } void SetRatioCandleSizeValue(const double value) { this.m_ratio_candle_sizes=value; } void SetMinBodySize(const uint value) { this.m_min_body_size=value; } //--- Return the necessary percentage ratio of the candle body to the full size of the candle, //--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body double RatioBodyToCandleSizeValue(void) const { return this.m_ratio_body_to_candle_size; } double RatioLargerShadowToCandleSizeValue(void) const { return this.m_ratio_larger_shadow_to_candle_size; } double RatioSmallerShadowToCandleSizeValue(void) const { return this.m_ratio_smaller_shadow_to_candle_size; } double RatioCandleSizeValue(void) const { return this.m_ratio_candle_sizes; } int MinBodySize(void) const { return (int)this.m_min_body_size; } //--- Return object ID based on pattern search criteria virtual ulong ObjectID(void) const { return this.m_object_id; } //--- Return pattern (1) type, (2) timeframe, (3) symbol, (4) symbol Point, (5) symbol code ENUM_PATTERN_TYPE TypePattern(void) const { return this.m_type_pattern; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } string Symbol(void) const { return this.m_symbol; } double Point(void) const { return this.m_point; } ulong SymbolCode(void) const { return this.m_symbol_code; } //--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low void SetChartScale(const int scale) { this.m_chart_scale=scale; } void SetChartHeightInPixels(const int height) { this.m_chart_height_px=height; } void SetChartWidthInPixels(const int width) { this.m_chart_width_px=width; } void SetChartPriceMax(const double price) { this.m_chart_price_max=price; } void SetChartPriceMin(const double price) { this.m_chart_price_min=price; } //--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low int ChartScale(void) const { return this.m_chart_scale; } int ChartHeightInPixels(void) const { return this.m_chart_height_px; } int ChartWidthInPixels(void) const { return this.m_chart_width_px; } double ChartPriceMax(void) const { return this.m_chart_price_max; } double ChartPriceMin(void) const { return this.m_chart_price_min; } //--- Compare CPatternControl objects by all possible properties virtual int Compare(const CObject *node,const int mode=0) const; //--- Search for patterns and add found ones to the list of all patterns virtual int CreateAndRefreshPatternList(void); //--- Display patterns on the chart void DrawPatterns(const bool redraw=false); //--- Redraw patterns on the chart with a new size void RedrawPatterns(const bool redraw=false); //--- Protected parametric constructor protected: CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]); };
Im parametrischen Konstruktor der Klasse ermittel wir die fehlenden Eigenschaften des Charts und füllen die Musterparameter aus dem an den Konstruktor übergebenen Array aus:
//+------------------------------------------------------------------+ //| CPatternControl::Protected parametric constructor | //+------------------------------------------------------------------+ CPatternControl::CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]) : m_used(true),m_drawing(true) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERN_CONTROL; this.m_type_pattern=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT); this.m_object_id=0; this.m_list_series=list_series; this.m_list_all_patterns=list_patterns; for(int i=0;i<(int)this.m_symbol.Length();i++) this.m_symbol_code+=this.m_symbol.GetChar(i); this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE); this.m_chart_height_px=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); this.m_chart_width_px=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_price_max=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MAX); this.m_chart_price_min=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MIN); //--- fill in the array of parameters with data from the array passed to constructor int count=::ArrayResize(this.PatternParams,::ArraySize(param)); for(int i=0;i<count;i++) { this.PatternParams[i].type = param[i].type; this.PatternParams[i].double_value = param[i].double_value; this.PatternParams[i].integer_value= param[i].integer_value; this.PatternParams[i].string_value = param[i].string_value; } }
Nehmen wir die erforderlichen Korrekturen in der Methode vor, die nach Mustern sucht und die gefundenen Muster zur Liste aller Muster hinzufügt:
//+------------------------------------------------------------------+ //| CPatternControl::Search for patterns and add | //| found ones to the list of all patterns | //+------------------------------------------------------------------+ int CPatternControl::CreateAndRefreshPatternList(void) { //--- If not used, leave if(!this.m_used) return 0; //--- Reset the timeseries event flag and clear the list of all timeseries pattern events this.m_is_event=false; this.m_list_events.Clear(); //--- Get the opening date of the last (current) bar datetime time_open=0; if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open)) return 0; //--- Get a list of all bars in the timeseries except the current one CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS); if(list==NULL || list.Total()==0) return 0; //--- "Mother" bar data structure MqlRates pattern_mother_bar_data={}; //--- Sort the resulting list by bar opening time list.Sort(SORT_BY_BAR_TIME); //--- In a loop from the latest bar, for(int i=list.Total()-1;i>=0;i--) { //--- get the next bar object from the list CBar *bar=list.At(i); if(bar==NULL) continue; //--- look for a pattern relative to the received bar ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data); //--- If there is no pattern, go to the next bar if(direction==WRONG_VALUE) continue; //--- Pattern found on the current bar of the loop //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol ulong code=this.GetPatternCode(direction,bar.Time()); //--- Set the pattern code to the sample this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code); //--- Sort the list of all patterns by the unique pattern code this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE); //--- search for a pattern in the list using a unique code int index=this.m_list_all_patterns.Search(&this.m_pattern_instance); //--- If there is no pattern equal to the sample in the list of all patterns if(index==WRONG_VALUE) { //--- Create the pattern object CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar); if(pattern==NULL) continue; //--- Sort the list of all patterns by time and insert the pattern into the list by its time this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); if(!this.m_list_all_patterns.InsertSort(pattern)) { delete pattern; continue; } //--- Add the pattern type to the list of pattern types of the bar object bar.AddPatternType(pattern.TypePattern()); //--- Add the pointer to the bar the pattern object is found on, together with the mother bar data pattern.SetPatternBar(bar); pattern.SetMotherBarData(pattern_mother_bar_data); //--- set the chart data to the pattern object pattern.SetChartHeightInPixels(this.m_chart_height_px); pattern.SetChartWidthInPixels(this.m_chart_width_px); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_price_max); pattern.SetChartPriceMin(this.m_chart_price_min); //--- If the drawing flag is set, draw the pattern label on the chart if(this.m_drawing) pattern.Draw(false); //--- Get the time of the penultimate bar in the collection list (timeseries bar with index 1) datetime time_prev=time_open-::PeriodSeconds(this.Timeframe()); //--- If the current bar in the loop is the bar with index 1 in the timeseries, create and send a message if(bar.Time()==time_prev) { // Here is where the message is created and sent } } } //--- Sort the list of all patterns by time and return the total number of patterns in the list this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); return m_list_all_patterns.Total(); }
Die Methode hat nun keine formalen Parameter mehr. Bei der Erstellung eines neuen Musters werden die Charteigenschaften sofort übernommen und in das Muster gesetzt. Ganz am Ende des Blocks der Mustersuchschleife fügen wir ein Leerzeichen für die Erzeugung eines neuen Musterereignisses ein. Außerdem werden wir in den folgenden Artikeln Ereignisse über das Auftreten eines neuen Zeitreihenmusters übermitteln.
Die Methode, mit der Muster auf einem Chart mit einer neuen Größe des Bitmap-Objekts neu gezeichnet werden:
//+-------------------------------------------------------------------+ //|Redraw patterns on a chart with a new size of the bitmap object | //+-------------------------------------------------------------------+ void CPatternControl::RedrawPatterns(const bool redraw=false) { //--- Get a list of patterns controlled by the control object CArrayObj *list=this.GetListPatterns(); if(list==NULL || list.Total()==0) return; //--- Sort the obtained list by pattern time list.Sort(SORT_BY_PATTERN_TIME); //--- In a loop from the latest pattern, for(int i=list.Total()-1;i>=0;i--) { //--- get the next pattern object CPattern *obj=list.At(i); if(obj==NULL) continue; //--- Redraw the pattern bitmap object on the chart obj.Redraw(false); } //--- At the end of the cycle, redraw the chart if the flag is set if(redraw) ::ChartRedraw(this.m_chart_id); }
Wir gehen einfach in einer Schleife durch die Liste der Zeitreihenmuster und rufen die Redraw-Methoden für jedes Muster auf. Die Methode ist zwar noch nicht optimal, aber das Neuzeichnen erfolgt über die gesamte Liste der Muster. Es ist jedoch möglich, nur den auf dem Chart sichtbaren Teil des Verlaufs neu zu zeichnen. Ich werde später darauf zurückkommen.
Nun wollen wir die Klassen für die Musterverwaltung verfeinern. Sie befinden sich weiter in derselben Datei. Suchen wir alle Vorkommen der Zeichenkette
const uint min_body_size
in den formalen Parametern der Klassenmethoden und löschen sie. Nun wird die Mindestgröße der gewünschten Musterkerze zusammen mit den Musterparametern im Array der Struktur MqlParam übergeben.
Markieren wir einfach alle Stellen, an denen Änderungen vorgenommen wurden, farbig, um hier nicht jeden einzelnen Code-String, der für alle Klassen gleich ist, zu beschreiben.
In der Klasse für die Verwaltung des Musters der „Pin Bar“:
//+------------------------------------------------------------------+ //| Pin Bar pattern control class | //+------------------------------------------------------------------+ class CPatternControlPinBar : public CPatternControl { protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor CPatternControlPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns, const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_PIN_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; this.m_ratio_body_to_candle_size=this.PatternParams[1].double_value; this.m_ratio_larger_shadow_to_candle_size=this.PatternParams[2].double_value; this.m_ratio_smaller_shadow_to_candle_size=this.PatternParams[3].double_value; this.m_ratio_candle_sizes=0; this.m_object_id=this.CreateObjectID(); } };
...
//+------------------------------------------------------------------+ //| CPatternControlPinBar::Search for the pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get data for one bar by time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL); //--- If the list is empty, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- he size of the candle body should be less than or equal to RatioBodyToCandleSizeValue() (default 30%) of the entire candle size, //--- in this case, the body size should not be less than min_body_size list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.RatioBodyToCandleSizeValue(),EQUAL_OR_LESS); list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.m_min_body_size,EQUAL_OR_MORE); //--- If the list is empty - there are no patterns, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Define the bullish pattern
In der Musterverwaltungsklasse der „Inside Bar“:
//+------------------------------------------------------------------+ //| Inside Bar pattern control class | //+------------------------------------------------------------------+ class CPatternControlInsideBar : public CPatternControl { private: //--- Check and return the presence of a pattern on two adjacent bars bool CheckInsideBar(const CBar *bar1,const CBar *bar0) const { //--- If empty bar objects are passed, return 'false' if(bar0==NULL || bar1==NULL) return false; //--- Return the fact that the bar on the right is completely within the dimensions of the bar on the left return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low()); } bool FindMotherBar(CArrayObj *list,MqlRates &rates) const { bool res=false; if(list==NULL) return false; //--- In a loop through the list, starting from the bar to the left of the base one for(int i=list.Total()-2;i>0;i--) { //--- Get the pointers to two consecutive bars CBar *bar0=list.At(i); CBar *bar1=list.At(i-1); if(bar0==NULL || bar1==NULL) return false; //--- If the obtained bars represent a pattern if(CheckInsideBar(bar1,bar0)) { //--- set mother bar data to the MqlRates variable and set 'res' to 'true' this.SetBarData(bar1,rates); res=true; } //--- If there is no pattern, interrupt the loop else break; } //--- return the result return res; } protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor CPatternControlInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_INSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; this.m_ratio_body_to_candle_size=0; this.m_ratio_larger_shadow_to_candle_size=0; this.m_ratio_smaller_shadow_to_candle_size=0; this.m_ratio_candle_sizes=0; this.m_object_id=this.CreateObjectID(); } };
...
//+------------------------------------------------------------------+ //| CPatternControlInsideBar::Search for pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get bar data up to and including the specified time
Ähnlich wie bei den Verwaltungsklassen der „Pin Bar“ und der „Inside Bar“ werden wir die Klasse für die Verwaltung des Musters „Outside Bar“ implementieren:
//+------------------------------------------------------------------+ //| Outside Bar pattern management class | //+------------------------------------------------------------------+ class CPatternControlOutsideBar : public CPatternControl { private: //--- Check and return the flag of compliance with the ratio of the candle body to its size relative to the specified value bool CheckProportions(const CBar *bar) const { return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue()); } //--- Return the ratio of nearby candles for the specified bar double GetRatioCandles(const CBar *bar) const { //--- If an empty object is passed, return 0 if(bar==NULL) return 0; //--- Get a list of bars with a time less than that passed to the method CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS); if(list==NULL || list.Total()==0) return 0; //--- Set the flag of sorting by bar time for the list and list.Sort(SORT_BY_BAR_TIME); //--- get the pointer to the last bar in the list (closest to the one passed to the method) CBar *bar1=list.At(list.Total()-1); if(bar1==NULL) return 0; //--- Return the ratio of the sizes of one bar to another return(bar.Size()>0 ? bar1.Size()*100.0/bar.Size() : 0.0); } //--- Check and return the presence of a pattern on two adjacent bars bool CheckOutsideBar(const CBar *bar1,const CBar *bar0) const { //--- If empty bar objects are passed, or their types in direction are neither bullish nor bearish, or are the same - return 'false' if(bar0==NULL || bar1==NULL || bar0.TypeBody()==BAR_BODY_TYPE_NULL || bar1.TypeBody()==BAR_BODY_TYPE_NULL || bar0.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar1.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar0.TypeBody()==bar1.TypeBody()) return false; //--- Calculate the ratio of the specified candles and, if it is less than the specified one, return 'false' double ratio=(bar0.Size()>0 ? bar1.Size()*100.0/bar0.Size() : 0.0); if(ratio<this.RatioCandleSizeValue()) return false; //--- Return the fact that the bar body on the right completely covers the dimensions of the bar body on the left, //--- and the shadows of the bars are either equal, or the shadows of the bar on the right overlap the shadows of the bar on the left return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody()); } protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor CPatternControlOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns, const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; // Minimum size of pattern candles this.m_ratio_candle_sizes=this.PatternParams[1].double_value; // Percentage of the size of the absorbing candle to the size of the absorbed one this.m_ratio_body_to_candle_size=this.PatternParams[2].double_value; // Percentage of full size to candle body size this.m_object_id=this.CreateObjectID(); } };
Die notwendigen Bedingungen für die Definition des Musters sind: Der Körper des Balkens auf der rechten Seite muss die Abmessungen des Körpers des Balkens auf der linken Seite vollständig überdecken, und die Schatten der Balken sind entweder gleich groß, oder die Schatten des Balkens auf der rechten Seite können die Schatten des Balkens auf der linken Seite überdecken. Diese Suche wird mit der Methode CheckOutsideBar() durchgeführt. Außerdem muss das Verhältnis zwischen den Größen der im Muster enthaltenen Kerzenkörper und der Gesamtgröße der Kerzen berücksichtigt werden. Diese Prüfung wird mit der Methode CheckProportions() durchgeführt.
Im Konstruktor der Klasse wird der Status des Musters auf „Price Action“ und der Typ des Musters auf „Outside Bar“ festgelegt und alle Kerzenproportionen des Musters aus dem Array der Strukturen, die an den Konstruktor übergeben werden, festgelegt.
Die Methode, die eine Objekt-ID auf der Grundlage von Mustersuchkriterien erstellt:
//+------------------------------------------------------------------+ //| Create object ID based on pattern search criteria | //+------------------------------------------------------------------+ ulong CPatternControlOutsideBar::CreateObjectID(void) { ushort bodies=(ushort)this.RatioCandleSizeValue()*100; ushort body=(ushort)this.RatioBodyToCandleSizeValue()*100; ulong res=0; this.UshortToLong(bodies,0,res); return this.UshortToLong(body,1,res); }
Zwei Kriterien (prozentuale Verhältnisse der Kerzengrößen und das Verhältnis des Kerzenkörpers zur vollen Kerzengröße) werden in realen Zahlen (in Prozent) angegeben und können 100 nicht überschreiten. Daher wandeln wir sie in Ganzzahlwerte um, indem wir sie mit 100 multiplizieren, und erstellen dann mit der erweiterten Standard-Objektbibliotheksmethode UshortToLong() eine Ullong-ID, indem wir die angegebenen Bits der Long-Nummer mit Ushort-Werten füllen:
//+------------------------------------------------------------------+ //| Pack a 'ushort' number to a passed 'long' number | //+------------------------------------------------------------------+ long CBaseObjExt::UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value) { if(to_byte>3) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_INDEX)); return 0; } return(long_value |= this.UshortToByte(ushort_value,to_byte)); } //+------------------------------------------------------------------+ //| Convert a 'ushort' value to a specified 'long' number byte | //+------------------------------------------------------------------+ long CBaseObjExt::UshortToByte(const ushort value,const uchar to_byte) const { return(long)value<<(16*to_byte); }
Die Methode, die das Muster mit der angegebenen Richtung erstellt:
//+------------------------------------------------------------------+ //| Create a pattern with a specified direction | //+------------------------------------------------------------------+ CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar) { //--- If invalid indicator is passed to the bar object, return NULL if(bar==NULL) return NULL; //--- Fill the MqlRates structure with bar data MqlRates rates={0}; this.SetBarData(bar,rates); //--- Create a new Outside Bar pattern CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction); if(obj==NULL) return NULL; //--- set the proportions of the candle the pattern was found on to the properties of the created pattern object obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,bar.RatioBodyToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,bar.RatioLowerShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,bar.RatioUpperShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES,this.GetRatioCandles(bar)); //--- set the search criteria of the candle the pattern was found on to the properties of the created pattern object obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,this.RatioBodyToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioLargerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioSmallerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,this.RatioCandleSizeValue()); //--- Set the control object ID to the pattern object obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID()); //--- Return the pointer to a created object return obj; }
Die Methode erstellt ein neues Objekt der Musterklasse „Outside Bar“, füllt Daten über seine Proportionen und Suchkriterien aus und gibt den Zeiger auf das erstellte Objekt zurück.
Methode der Mustersuche:
//+------------------------------------------------------------------+ //| CPatternControlOutsideBar::Search for pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get bar data up to and including the specified time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS); //--- If the list is empty, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Sort the list by bar opening time list.Sort(SORT_BY_BAR_TIME); //--- Get the latest bar from the list (base bar) CBar *bar_patt=list.At(list.Total()-1); if(bar_patt==NULL) return WRONG_VALUE; //--- In the loop from the next bar (mother), for(int i=list.Total()-2;i>=0;i--) { //--- Get the "mother" bar CBar *bar_prev=list.At(i); if(bar_prev==NULL) return WRONG_VALUE; //--- If the proportions of the bars do not match the pattern, return -1 if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev)) return WRONG_VALUE; //--- check that the resulting two bars are a pattern. If not, return -1 if(!this.CheckOutsideBar(bar_prev,bar_patt)) return WRONG_VALUE; //--- Set the "mother" bar data this.SetBarData(bar_prev,mother_bar_data); //--- If the pattern is found at the previous step, determine and return its direction if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH) return PATTERN_DIRECTION_BULLISH; if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH) return PATTERN_DIRECTION_BEARISH; } //--- No patterns found - return -1 return WRONG_VALUE; }
Die Logik der Methode ist in den Kommentaren beschrieben. Die Eröffnungszeit des aktuellen Balkens wird an die Methode übergeben. Sie holt die Liste aller Balken außer dem aktuellen Balken (wir suchen nicht nach Mustern auf dem Null-Balken). In der Schleife, die auf der resultierenden Liste der Balken basiert, werden alle zwei benachbarten Balken daraufhin überprüft, ob sie die Kriterien des Musters erfüllen. Wenn ihr Verhältnis ein Muster ist, bestimmen wir die Richtung des Musters und geben es zurück.
Die Methode gibt eine Liste von Mustern zurück, die von dem Objekt verwaltet werden:
//+------------------------------------------------------------------+ //| Returns the list of patterns managed by the object | //+------------------------------------------------------------------+ CArrayObj *CPatternControlOutsideBar::GetListPatterns(void) { CArrayObj *list=CSelect::ByPatternProperty(this.m_list_all_patterns,PATTERN_PROP_PERIOD,this.Timeframe(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_SYMBOL,this.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_TYPE,PATTERN_TYPE_OUTSIDE_BAR,EQUAL); return CSelect::ByPatternProperty(list,PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID(),EQUAL); }
Aus der allgemeinen Liste aller Muster erhalten wir eine Liste, die nach der Chartperiode sortiert ist, und aus der resultierenden Liste erhalten wir eine Liste, die nach dem Symbolnamen sortiert ist. Als Nächstes wird die resultierende Liste nach dem Mustertyp „Outside Bar“ und die resultierende Liste nach der ID des Steuerungsobjekts sortiert. Als Ergebnis liefert die Methode eine Liste mit nur den Mustern, die von dieser Klasse verwaltet werden.
Nun müssen wir die Klasse der Musterverwaltung komplett überarbeiten. Mehr darüber können Sie hier lesen.
Anstelle langer und eintöniger Methoden für die Behandlung der einzelnen Mustertypen werden wir mehrere Methoden für die Behandlung des angegebenen Musters erstellen. Dann werden wir in derselben Datei zahlreiche ähnliche Methoden aus der Mustermanagementklasse entfernen, wie z. B.:
... "Return the ... pattern control object" CPatternControl *GetObjControlPattern ...XXXXX(), ... "Set the flag for using the ... pattern and create a control object if it does not already exist" void SetUsedPattern ... XXXXX(const bool flag), ... "Return the flag of using the ... pattern" bool IsUsedPattern ...XXXXX(void), ... "Set the flag for drawing the ... pattern with dots" void SetDrawingAsDotsPattern ...XXXXX(const bool flag,const bool redraw), ... "Return the flag for drawing the ... pattern with dots" bool IsDrawingAsDotsPattern ...XXXXX(void), ... "Set ... pattern labels on the chart" void DrawPattern ...XXXXX(const bool redraw=false)
Im Allgemeinen gibt es eine Vielzahl solcher Methoden - jedes Muster hat seine eigene Methode. Das Ergebnis: fast 1300 Code-Zeilen. Entfernen wir alle Zeichenketten aus der Musterverwaltungsklasse und schreiben wir die gesamte Klasse neu. Jetzt gibt es mehrere Methoden für die Handhabung von Mustern, mit der Möglichkeit zu wählen, mit welchem Muster man arbeiten möchte.
Die komplett neu geschriebene Klasse mit all ihren Methoden sieht nun wie folgt aus:
//+------------------------------------------------------------------+ //| Pattern control class | //+------------------------------------------------------------------+ class CPatternsControl : public CBaseObjExt { private: CArrayObj m_list_controls; // List of pattern management controllers CArrayObj *m_list_series; // Pointer to the timeseries list CArrayObj *m_list_all_patterns; // Pointer to the list of all patterns //--- Timeseries data ENUM_TIMEFRAMES m_timeframe; // Timeseries timeframe string m_symbol; // Timeseries symbol public: //--- Return (1) timeframe, (2) timeseries symbol, (3) itself ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } string Symbol(void) const { return this.m_symbol; } CPatternsControl *GetObject(void) { return &this; } protected: //--- Create an object for managing a specified pattern CPatternControl *CreateObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { switch(pattern) { case PATTERN_TYPE_HARAMI : return NULL; case PATTERN_TYPE_HARAMI_CROSS : return NULL; case PATTERN_TYPE_TWEEZER : return NULL; case PATTERN_TYPE_PIERCING_LINE : return NULL; case PATTERN_TYPE_DARK_CLOUD_COVER : return NULL; case PATTERN_TYPE_THREE_WHITE_SOLDIERS : return NULL; case PATTERN_TYPE_THREE_BLACK_CROWS : return NULL; case PATTERN_TYPE_SHOOTING_STAR : return NULL; case PATTERN_TYPE_HAMMER : return NULL; case PATTERN_TYPE_INVERTED_HAMMER : return NULL; case PATTERN_TYPE_HANGING_MAN : return NULL; case PATTERN_TYPE_DOJI : return NULL; case PATTERN_TYPE_DRAGONFLY_DOJI : return NULL; case PATTERN_TYPE_GRAVESTONE_DOJI : return NULL; case PATTERN_TYPE_MORNING_STAR : return NULL; case PATTERN_TYPE_MORNING_DOJI_STAR : return NULL; case PATTERN_TYPE_EVENING_STAR : return NULL; case PATTERN_TYPE_EVENING_DOJI_STAR : return NULL; case PATTERN_TYPE_THREE_STARS : return NULL; case PATTERN_TYPE_ABANDONED_BABY : return NULL; case PATTERN_TYPE_PIVOT_POINT_REVERSAL : return NULL; case PATTERN_TYPE_OUTSIDE_BAR : return new CPatternControlOutsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_INSIDE_BAR : return new CPatternControlInsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_PIN_BAR : return new CPatternControlPinBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_RAILS : return NULL; //---PATTERN_TYPE_NONE default : return NULL; } } //--- Return an object for managing a specified pattern CPatternControl *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { //--- In a loop through the list of control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next object CPatternControl *obj=this.m_list_controls.At(i); //--- if this is not a pattern control object, go to the next one if(obj==NULL || obj.TypePattern()!=pattern) continue; //--- Check search conditions and return the result if(IsEqualMqlParamArrays(obj.PatternParams,param)) return obj; } //--- Not found - return NULL return NULL; } public: //--- Search and update all active patterns void RefreshAll(void) { //--- In a loop through the list of pattern control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next control object CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- if this is a tester and the current chart sizes are not set, or are not equal to the current ones - we try to get and set them if(::MQLInfoInteger(MQL_TESTER)) { long int_value=0; double dbl_value=0; if(::ChartGetInteger(this.m_chart_id, CHART_SCALE, 0, int_value)) { if(obj.ChartScale()!=int_value) obj.SetChartScale((int)int_value); } if(::ChartGetInteger(this.m_chart_id, CHART_HEIGHT_IN_PIXELS, 0, int_value)) { if(obj.ChartHeightInPixels()!=int_value) obj.SetChartHeightInPixels((int)int_value); } if(::ChartGetInteger(this.m_chart_id, CHART_WIDTH_IN_PIXELS, 0, int_value)) { if(obj.ChartWidthInPixels()!=int_value) obj.SetChartWidthInPixels((int)int_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX, 0, dbl_value)) { if(obj.ChartPriceMax()!=dbl_value) obj.SetChartPriceMax(dbl_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN, 0, dbl_value)) { if(obj.ChartPriceMin()!=dbl_value) obj.SetChartPriceMin(dbl_value); } } //--- search and create a new pattern obj.CreateAndRefreshPatternList(); } } //--- Set chart parameters for all pattern management objects void SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { //--- In a loop through the list of pattern control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next control object CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- If the object is received, set the chart parameters obj.SetChartHeightInPixels(height_px); obj.SetChartWidthInPixels(width_px); obj.SetChartScale(scale); obj.SetChartPriceMax(price_max); obj.SetChartPriceMin(price_min); } } //--- Set the flag for using the specified pattern and create a control object if it does not already exist void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool flag) { CPatternControl *obj=NULL; //--- Get the pointer to the object for managing the specified pattern obj=this.GetObjControlPattern(pattern,param); //--- If the pointer is received (the object exists), set the use flag if(obj!=NULL) obj.SetUsed(flag); //--- If there is no object and the flag is passed as 'true' else if(flag) { //--- Create a new pattern management object obj=this.CreateObjControlPattern(pattern,param); if(obj==NULL) return; //--- Add pointer to the created object to the list if(!this.m_list_controls.Add(obj)) { delete obj; return; } //--- Set the usage flag and pattern parameters to the control object obj.SetUsed(flag); obj.CreateAndRefreshPatternList(); } } //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { CPatternControl *obj=this.GetObjControlPattern(pattern,param); return(obj!=NULL ? obj.IsUsed() : false); } //--- Places marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.DrawPatterns(redraw); } //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.RedrawPatterns(redraw); } //--- Constructor CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPatternsControl::CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERNS_CONTROLLERS; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_list_series=list_timeseries; this.m_list_all_patterns=list_all_patterns; } //+------------------------------------------------------------------+
Die Logik der Methoden ist im Code kommentiert. Ich möchte einige Klarstellungen bezüglich des hervorgehobenen Codeblocks machen: Wenn man im visuellen Modus des Testers arbeitet, ist OnChartEvent() fast nutzlos. Dort verfolgen wir jedoch die Änderung der Chartgröße in Bezug auf das Ereignis CHARTEVENT_CHART_CHANGE und schreiben die neuen Chartgrößen in das Musterverwaltungsobjekt, von wo aus diese Daten an die Musterobjekte weitergeleitet werden. Aber im Tester funktioniert das nicht. Daher werden Änderungen in der Größe der Karte im speziellen Tester-Codeblock überwacht, und wenn sie sich ändert, werden neue Daten in die Mustersteuerungsobjekte eingegeben.
Weiter im Code in derselben Datei \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh befindet sich die Zeitreihenklasse CSeriesDE. Bringen wir es zu Ende.
Jedes Mal, wenn wir nach einem Balkenobjekt in der Zeitreihenliste suchen, haben wir ein neues Objekt mit Hilfe des Operators new. Seine Parameter werden eingestellt, ein Objekt mit identischen Parametern wird in der Liste gesucht , und dann wird dieses neu erstellte Objekt gelöscht:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(); if(obj==NULL) return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; return this.m_list_series.At(index); }
So kommt es in manchen Situationen zu einer ständigen Erstellen und Löschen eines Objekts in einer Schleife. Dieses Verhalten ist falsch. Es ist besser, ein einzelnes Instanzobjekt für die Suche zu erstellen und es zum Festlegen von Parametern und als Muster für die Suche zu verwenden. Auf diese Weise werden wir das ständige Neuerstelen von Objekten los.
Deklarieren wir eine Instanz des Balken-Objekts für die Suche:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: CBar m_bar_tmp; // Bar object for search ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol
und schreiben die Methode um, die das Balkenobjekt nach Zeit in der Zeitreihe zurückgibt:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { this.m_bar_tmp.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(&this.m_bar_tmp); return this.m_list_series.At(index); }
Anstatt ein neues Objekt zu erstellen und zu löschen, legen wir die erforderlichen Suchparameter in einer einzigen Instanz fest und suchen nach einem Muster nach einer identischen Instanz in der Liste. Das Objekt wird nur einmal im Klassenkonstruktor erstellt und wird kontinuierlich verwendet, ohne neu erstellt zu werden.
Genau wie in der Musterverwaltungsklasse werden wir die langen Listen von Methoden für die Behandlung jedes Musters entfernen und sie durch einige wenige Methoden ersetzen, die das gewünschte Muster auswählen:
//+------------------------------------------------------------------+ //| Working with patterns | //+------------------------------------------------------------------+ //--- Set the flag for using the specified pattern and create a control object if it does not already exist void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool flag) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetUsedPattern(pattern,param,flag); } //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : false); } //--- Places marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { if(this.m_patterns_control!=NULL) this.m_patterns_control.DrawPattern(pattern,param,redraw); } //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { if(this.m_patterns_control!=NULL) this.m_patterns_control.RedrawPattern(pattern,param,redraw); } //--- Sets chart parameters for pattern management objects void SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); }
Um das gewünschte Muster auszuwählen, genügt es nun, seinen Typ in den Methodenparametern anzugeben, anstatt für jedes Muster eine eigene Methode zu verwenden.
Wir ändern in der Klasse der Zeitreihen des Symbols, in D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh, die Methoden für die Behandlung von Mustern auf die gleiche Weise.
Im Hauptteil der Klasse, ganz am Ende, unter dem Header
//--- Constructors CTimeSeriesDE(CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL; this.m_list_all_patterns=list_all_patterns; } CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol); //+------------------------------------------------------------------+ //| Methods for handling patterns | //+------------------------------------------------------------------+
gibt es eine lange Liste von Methodendeklarationen zur Behandlung von Mustern. Ersetzen wir alle diese Deklarationen durch die Deklaration mehrerer Methoden mit einer Auswahl des gewünschten Musters:
//--- Constructors CTimeSeriesDE(CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL; this.m_list_all_patterns=list_all_patterns; } CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol); //+------------------------------------------------------------------+ //| Methods for handling patterns | //+------------------------------------------------------------------+ //--- Set the flag for using the specified pattern and create a control object if it does not already exist void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Return the flag of using the specified Harami pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe); //--- Draw marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Set chart parameters for pattern management objects on the specified timeframe void SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px); };
Außerhalb des Klassenkörpers, ganz am Ende der Datei, unter derselben Kopfzeile, werden die Implementierungen der deklarierten Methoden geschrieben. Streichen wir die gesamte lange Liste und ersetzen sie durch neue Methoden:
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Set the flag of using the specified pattern | //| and create a control object if it does not exist yet | //+------------------------------------------------------------------+ void CTimeSeriesDE::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool flag) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetUsedPattern(pattern,param,flag); } //+------------------------------------------------------------------+ //| Return the flag of using the specified pattern | //+------------------------------------------------------------------+ bool CTimeSeriesDE::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.GetSeries(timeframe); return(series!=NULL ? series.IsUsedPattern(pattern,param) : false); } //+------------------------------------------------------------------+ //| Draw marks of the specified pattern on the chart | //+------------------------------------------------------------------+ void CTimeSeriesDE::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.DrawPattern(pattern,param,redraw); } //+------------------------------------------------------------------+ //| Redraw the bitmap objects of the specified pattern on the chart | //+------------------------------------------------------------------+ void CTimeSeriesDE::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.RedrawPattern(pattern,param,redraw); } //+------------------------------------------------------------------+ //| Set chart parameters for pattern management objects | //| on the specified timeframe | //+------------------------------------------------------------------+ void CTimeSeriesDE::SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); }
Jede Methode erhält den gewünschten Zeitrahmen des Charts, dessen Muster verarbeitet werden sollen, sowie die erforderlichen Parameter zur Auswahl des Mustersteuerungsobjekts. Anschließend wird ein Zeiger auf die gewünschte Zeitreihe ermittelt und das Ergebnis des Aufrufs der gleichnamigen Methode der Klasse der Zeitreihe zurückgegeben.
Nehmen wir einige Verbesserungen in der Datei \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh für die Zeitreihensammlung vor.
In dieser Klasse erhalten wir die Chartgrößen und ihre Änderungen und senden sie an die Klassen der Musterverwaltung. Fügen wir neue Variablen hinzu, um die Chartgrößen zu speichern:
//+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt { private: CListObj m_list; // List of applied symbol timeseries CListObj m_list_all_patterns; // List of all patterns of all used symbol timeseries CChartObjCollection *m_charts; // Pointer to the chart collection double m_chart_max; // Chart maximum double m_chart_min; // Chart minimum int m_chart_scale; // Chart scale int m_chart_wpx; // Chart width in pixels int m_chart_hpx; // Chart height in pixels //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public:
Unter der Überschrift
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
genau wie in den vorherigen Klassen entfernen wir die deklarierten Methoden und geben neue Methoden mit Angabe des Mustertyps ein:
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //--- Set the flag of using the specified pattern void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Draw marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Set chart parameters for pattern management objects on the specified symbol and timeframe void SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px);
Am Ende des Hauptteils der Klasse deklarieren wir die Ereignisbehandlung:
//--- Initialization void OnInit(CChartObjCollection *charts) { this.m_charts=charts; } //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CTimeSeriesCollection(void); };
Im Klassenkonstruktor schreiben wir die Größen des Charts in die neuen Variablen:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeriesCollection::CTimeSeriesCollection(void) { this.m_type=COLLECTION_SERIES_ID; this.m_list.Clear(); this.m_list.Sort(); this.m_list.Type(COLLECTION_SERIES_ID); this.m_list_all_patterns.Clear(); this.m_list_all_patterns.Sort(); this.m_list_all_patterns.Type(COLLECTION_SERIES_PATTERNS_ID); this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE);//-1; this.m_chart_max=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX); this.m_chart_min=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN); this.m_chart_wpx=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_hpx=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); }
Außerhalb des Hauptteils der Klasse, unter dem Kopf
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
entfernen wir alle Methoden für die Behandlung jedes spezifischen Musters. Anstelle der entfernten Methoden legen wir neue Methoden fest, die den Mustertyp angeben:
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Set the flag of using the specified pattern | //| and create a control object if it does not exist yet | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetUsedPattern(pattern,param,timeframe,flag); } //+------------------------------------------------------------------+ //| Return the flag of using the specified pattern | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); return(timeseries!=NULL ? timeseries.IsUsedPattern(pattern,param,timeframe) : false); } //+------------------------------------------------------------------+ //| Draw marks of the specified pattern on the chart | //+------------------------------------------------------------------+ void CTimeSeriesCollection::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.DrawPattern(pattern,param,timeframe,redraw); } //+------------------------------------------------------------------+ //| Redraw the bitmap objects of the specified pattern on the chart | //+------------------------------------------------------------------+ void CTimeSeriesCollection::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.RedrawPattern(pattern,param,timeframe,redraw); } //+------------------------------------------------------------------+ //| Set chart parameters for pattern management objects | //| on the specified symbol and timeframe | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetChartPropertiesToPattCtrl(timeframe,price_max,price_min,scale,height_px,width_px); }
Jede Methode erhält den gewünschten Zeitrahmen und das Symbol des Charts, dessen Muster verarbeitet werden sollen, sowie die erforderlichen Parameter zur Auswahl des Musterkontrollobjekts. Anschließend wird ein Zeiger auf die benötigte Zeitreihe abgerufen und das Ergebnis des Aufrufs der gleichnamigen Methode der Klasse der Zeitreihe des Symbol zurückgegeben.
Implementieren wir nun die Ereignisbehandlung:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CTimeSeriesCollection::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the current chart object CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID()); if(chart==NULL) return; //--- Get the main window object of the current chart CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),0); if(wnd==NULL) return; //--- If the chart is changed bool res=false; if(id==CHARTEVENT_CHART_CHANGE) { //--- control the change in the chart scale int scale=(int)::ChartGetInteger(chart.ID(),CHART_SCALE); if(this.m_chart_scale!=scale) { this.m_chart_scale=scale; res=true; } //--- control the change in the chart width in pixels int chart_wpx=(int)::ChartGetInteger(chart.ID(),CHART_WIDTH_IN_PIXELS); if(this.m_chart_wpx!=chart_wpx) { this.m_chart_wpx=chart_wpx; res=true; } //--- control the change in the chart height in pixels int chart_hpx=(int)::ChartGetInteger(chart.ID(),CHART_HEIGHT_IN_PIXELS); if(this.m_chart_hpx!=chart_hpx) { this.m_chart_hpx=chart_hpx; res=true; } //--- control the change in the chart maximum double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX); if(this.m_chart_max!=chart_max) { this.m_chart_max=chart_max; res=true; } //--- control the change in the chart minimum double chart_min=::ChartGetDouble(chart.ID(),CHART_PRICE_MIN); if(this.m_chart_min!=chart_min) { this.m_chart_min=chart_min; res=true; } //--- If there is at least one change if(res) { //--- Write new values of the chart properties to the pattern management objects this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx); //--- Get a list of patterns on the current chart CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL); //--- If the list of patterns is received if(list!=NULL) { //--- In a loop by the list of patterns, int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next pattern object CPattern *pattern=list.At(i); if(pattern==NULL) continue; //--- set the new chart data to the pattern object pattern.SetChartWidthInPixels(this.m_chart_wpx); pattern.SetChartHeightInPixels(this.m_chart_hpx); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_max); pattern.SetChartPriceMin(this.m_chart_min); //--- Redraw the pattern bitmap object with new dimensions pattern.Redraw(false); } //--- Update the chart ::ChartRedraw(chart.ID()); } } } }
Die gesamte Logik der Ereignisbehandlung ist in den Kommentaren zum Code ausführlich beschrieben. Wenn hier eine Änderung der Chartgröße festgestellt wird, werden die neuen Größen an die Zeitreihenmuster-Verwaltungsklassen weitergegeben und die Muster entsprechend der neuen Chartgröße neu gezeichnet.
Ändern wir nun die Hauptklasse der CEngine-Bibliothek in \MQL5\Include\DoEasy\Engine.mqh.
Auch hier gibt es im Klassenkörper eine lange Liste von Methoden zur Behandlung von Mustern. Wir wollen nur die Methoden entfernen, die für das Zeichnen von Mustern in Form von Punkten zuständig sind. Die verbleibenden Methoden müssen verbessert werden - jetzt ist es notwendig, Musterparameter an die Methoden zu senden, die sie mit Hilfe des Strukturarrays MqlParam verarbeiten. Bei allen Methoden, die noch nicht verwendet werden, füllen wir nur ein Strukturfeld, in dem z.B. die Mindestgröße der Musterkerzen gesendet wird:
//--- Set the flag for using the Harami pattern and create a control object if it does not already exist void SeriesSetUsedPatternHarami(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=0; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_HARAMI,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Die übrigen Methoden für die noch nicht verwendeten Muster sind identisch und müssen nicht berücksichtigt werden - Sie finden sie in den Dateien im Anhang des Artikels.
Betrachten wir die Methoden zur Behandlung der bereits in der Bibliothek erstellten Muster.
Die Methode, die das Flag für die Verwendung des Musters Externer Balken (Engulfing) setzt und ein Kontrollobjekt erstellt, falls dieses noch nicht existiert:
//--- Set the flag for using the Pattern Outside and create a control object if it does not already exist void SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Price Action External Bar usage flag const double ratio_candles=70, // Percentage ratio of the size of the engulfing color to the size of the engulfed one const double ratio_body_to_shadows=80, // Percentage ratio of shadow sizes to candle body size const uint min_body_size=3) // Minimum candle body size { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Percentage ratio of shadow sizes to candle body size param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_body_to_shadows; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Die Methode setzt das Flag für die Verwendung des Inside Bar-Musters und die Erstellung das Kontrollobjekt, falls es noch nicht existiert:
//--- Set the flag for using the Pattern Inside and create a control object if it does not already exist void SeriesSetUsedPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag,const uint min_body_size=3) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Bei dieser Methode wird nur ein Feld der Struktur ausgefüllt, da das Muster keine anpassbaren Eigenschaften hat, mit Ausnahme der Eigenschaft der minimalen Kerzengröße, die allen Mustern gemeinsam ist.
Die Methode setzt das Flag für die Verwendung des Pin Bar-Musters und die Erstellung das Kontrollobjekt, wenn es noch nicht existiert:
//--- Set the flag for using the Pin Bar pattern and create a control object if it does not already exist void SeriesSetUsedPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Price Action Pin Bar usage flag const double ratio_body=30, // Percentage ratio of the candle body to the full size of the candle const double ratio_larger_shadow=60, // Percentage ratio of the size of the larger shadow to the size of the candle const double ratio_smaller_shadow=30, // Percentage ratio of the size of the smaller shadow to the size of the candle const uint min_body_size=3) // Minimum candle body size { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the candle body to the full size of the candle param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Percentage ratio of the size of the larger shadow to the size of the candle param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Percentage ratio of the size of the smaller shadow to the size of the candle param[3].type=TYPE_DOUBLE; param[3].double_value=ratio_smaller_shadow; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Betrachten wir nun die Methoden zur Darstellung von Mustern in einem Chart:
- für bereits implementierte Muster in der Bibliothek
- für ein Muster, das noch nicht erstellt worden ist:
//--- Draw Pattern Outside labels on the chart void SeriesDrawPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_candles=70, // Percentage ratio of the size of the engulfing color to the size of the engulfed one const double ratio_body_to_shadows=80, // Percentage ratio of shadow sizes to candle body size const uint min_body_size=3, // Minimum candle body size const bool redraw=false) // Chart redraw flag { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Percentage ratio of shadow sizes to candle body size param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_body_to_shadows; } this.m_time_series.DrawPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } //--- Draw Inside Bar pattern labels on the chart void SeriesDrawPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint min_body_size=3,const bool redraw=false) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; } this.m_time_series.DrawPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } //--- Draw Pin Bar pattern labels on the chart void SeriesDrawPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_body=30, // Percentage ratio of the candle body to the full size of the candle const double ratio_larger_shadow=60, // Percentage ratio of the size of the larger shadow to the size of the candle const double ratio_smaller_shadow=30, // Percentage ratio of the size of the smaller shadow to the size of the candle const uint min_body_size=3, // Minimum candle body size const bool redraw=false) // Chart redraw flag { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the candle body to the full size of the candle param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Percentage ratio of the size of the larger shadow to the size of the candle param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Percentage ratio of the size of the smaller shadow to the size of the candle param[3].type=TYPE_DOUBLE; param[3].double_value=ratio_smaller_shadow; } this.m_time_series.DrawPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); if(redraw) ::ChartRedraw(); } //--- Draw Rails pattern labels on the chart void SeriesDrawPatternRails(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=0; } this.m_time_series.DrawPattern(PATTERN_TYPE_RAILS,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); }
Die übrigen Methoden für andere Muster sind identisch mit der Methode für das noch nicht implementierte Rails-Muster, und es ist nicht sinnvoll, sie hier zu betrachten.
Ganz am Ende des Hauptteil der Klasse deklarieren wir die Ereignisbehandlung:
public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Handling DoEasy library events void OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Working with events in the tester void EventsHandling(void); };
Nun schreiben wir die Implementierung außerhalb des Hauptteils der Klasse:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CEngine::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { this.m_graph_objects.OnChartEvent(id,lparam,dparam,sparam); this.m_time_series.OnChartEvent(id,lparam,dparam,sparam); }
Hier wird zuerst die Ereignisbehandlung der Klasse der grafischen Objektsammlung aufgerufen, gefolgt von der Ereignisbehandlung der Zeitreihensammlung.
In der Ereignisbehandlung der Bibliothek CEngine::OnDoEasyEvent(), d. h. im Block für die Behandlung von Zeitreihenereignissen, fügen wir den Block für die künftige Behandlung von Ereignissen mit neuen Mustern hinzu:
//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); CArrayObj *list=this.m_buffers.GetListBuffersWithID(); if(list!=NULL) { int total=list.Total(); for(int i=0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) continue; string symbol=sparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; if(buff.Symbol()==symbol && buff.Timeframe()==timeframe ) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) continue; int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated())); this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count); } } } } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam); } //--- "New pattern" event if(idx==SERIES_EVENTS_PATTERN) { // New pattern event is handled here } }
Noch steht hier nichts, aber wenn wir in Zukunft Musterereignisse senden, werden wir das Auftreten eines neuen Musters hier behandeln.
Damit ist die Änderung der Bibliothek abgeschlossen. Überprüfen wir nun die Suche nach neuen Mustern und den Umgang mit Änderungen in der horizontalen Skala des Charts.
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn im neuen Ordner \MQL5\Experts\TestDoEasy\Part136\ als TestDoEasy136.mq5.
Wir entfernen das Flag für das Zeichnen von Mustern mit Punkten aus den Einstellungen:
sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput bool InpDrawPatternsAsDots= true; // Draw Patterns as dots
Die Flags für die Verwendung von Mustern fügen wir zu den Einstellungen hinzu, wie auch die Parametern für die Suche nach dem Muster „Outside Bar“:
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 ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_CURRENT; // Mode of used timeframes list sinput string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator) sinput ENUM_INPUT_YES_NO InpSearchPinBar = INPUT_YES; // Search for Pin Bar patterns sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput ENUM_INPUT_YES_NO InpSearchInsideBar = INPUT_YES; // Search for Inside Bar patterns sinput ENUM_INPUT_YES_NO InpSearchOutsideBar = INPUT_YES; // Search for Outside Bar patterns sinput double InpOBRatioCandles = 50.0; // Outside Bar Ratio of sizes of neighboring candles sinput double InpOBRatioBodyToCandle= 50.0; // Outside Bar Ratio Body to Candle size sinput ENUM_INPUT_YES_NO InpUseBook = INPUT_NO; // Use Depth of Market sinput ENUM_INPUT_YES_NO InpUseMqlSignals = INPUT_NO; // Use signal service sinput ENUM_INPUT_YES_NO InpUseCharts = INPUT_NO; // Use Charts control sinput ENUM_INPUT_YES_NO InpUseSounds = INPUT_YES; // Use sounds
Da die Methoden zum Setzen der Verwendung von Mustern sofort nach den gefundenen Mustern suchen und diese anzeigen, wenn das Flag gesetzt ist, reicht es für den Test aus, die Flags in OnInit() zu setzen, um sofort mit der Suche nach Mustern und deren Anzeige im Chart zu beginnen:
//--- Clear the list of all patterns engine.GetListAllPatterns().Clear(); //--- Set the flag of using the Pin Bar pattern with the parameters specified in the settings engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller); //--- Set the flag of using the Inside Bar pattern engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchInsideBar); //--- Set the flag of using the Outside Bar pattern engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle); //--- ChartRedraw(); return(INIT_SUCCEEDED); }
In dem EA OnChartEvent() fügen wir den Aufruf dieser Ereignisbehandlung für das Hauptobjekt der Engine-Bibliothek hinzu:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- Handling mouse events if(id==CHARTEVENT_OBJECT_CLICK) { //--- Handle pressing the buttons in the panel if(StringFind(sparam,"BUTT_")>0) PressButtonEvents(sparam); } //--- Handling DoEasy library events if(id>CHARTEVENT_CUSTOM-1) { OnDoEasyEvent(id,lparam,dparam,sparam); } engine.OnChartEvent(id,lparam,dparam,sparam); //--- Chart change if(id==CHARTEVENT_CHART_CHANGE) { //--- Whenever the chart changes, hide all information panels //... ... ...
In der Ereignisbehandlung der Bibliothek - die Funktion OnDoEasyEvent()
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
im Block für die Behandlung von Zeitreihenereignissen fügen wir eine Meldung über verpasste Balken hinzu, wenn ein solches Ereignis eintritt:
//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam); } } //--- Handle chart auto events
Im Moment bietet diese Funktion keine technischen Vorteile - sie zeigt lediglich die Anzahl der fehlenden Balken im Journal an. Aber es ist klar, dass die Bibliothek korrekt mit Balkensprüngen umgeht, wenn z. B. die Verbindung getrennt wurde, während der EA im Terminal lief, oder der Computer in den Ruhezustand versetzt und dann wieder aus diesem herausgeholt wurde. Das bedeutet, dass beim Eintreffen eines solchen Ereignisses die erforderliche Anzahl von Balken aktualisiert werden kann, um die fehlenden Zeitreihen und Musterdaten wiederherzustellen. Dies wird in Zukunft umgesetzt werden.
Kompilieren wir den EA und starten ihn, indem wir die folgenden Werte für die Suche nach dem Outside Bar-Muster einstellen:
Wir setzen bewusst so kleine Werte für die Kerzenanteile, damit möglichst viele Muster gefunden werden.
Bei normalen Werten von Kerzenanteilen (50 % oder mehr) sind die Muster korrekter, aber recht selten.
Nach dem Start werden die Muster „Outside Bar“ gefunden und angezeigt:
Wir sehen, dass die Muster gefunden werden. Wenn Sie die Größe des Charts ändern, ändern sich auch die Größen der Mustersymbole.
Was kommt als Nächstes?
Im nächsten Artikel über Kursformationen werden wir damit fortfahren, verschiedene Muster zu erstellen und Ereignisse zu senden, wenn sie auftreten.
Alle erstellten Dateien sind dem Artikel beigefügt und können zum Selbststudium und für Tests heruntergeladen werden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/14710





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.