
Disegnare i canali - Vista interna ed esterna
Introduzione
Suppongo che non risulterà esagerato dire che i canali sono lo strumento più popolare per l'analisi del mercato e per prendere decisioni di trading dopo le medie mobili. Nel primo articolo della serie dedicato ai canali, discuteremo le basi matematiche e l'implementazione teorica di un indicatore che disegna un canale impostato da tre estremi sullo schermo del client terminal.
A primo impatto, disegnare un canale sembra essere un compito facile dato che si basa sull'equazione di una linea retta, insegnata fin dalle elementari. Tuttavia, la sua implementazione pratica nel client terminal comporta molte domande a cui non è possibile rispondere in modo diretto.
Come si organizzano al meglio l'impostazione dell'estremo e il monitoraggio dei loro cambiamenti? Cosa si fa e come si disegna un canale se la sua parte centrale si trova sulle barre mancanti? Cosa succede se l'estremità sinistra di un canale è il venerdì e quella destra è il lunedì e ,quindi, i giorni liberi senza barre sono in mezzo? Come possiamo ottenere i valori correnti dei bordi di un canale?
Queste e altre domande trovano risposta nel primo della serie di articoli sui canali. Qui troverai anche l'implementazione del disegno dei canali con tre estremi specificati utilizzando le classi standard e l'approccio orientato agli oggetti. Implementeremo il disegno del canale sotto forma di indicatore.
Impostazione degli estremi
Di fatto, la posizione di un canale su un grafico è determinata da almeno tre estremi. Se diamo una definizione a un estremo, allora possiamo accettare che esso sia il valore massimo o minimo di una funzione su un dato intervallo. Il punto in cui viene raggiunto un estremo è chiamato punto estremo. Rispettivamente, se viene raggiunto un minimo, il punto estremo verrà chiamato punto minimo, se è un massimo, allora verrà chiamato punto massimo.
L'analisi matematica fornisce la definizione di un altro termine, ovvero estremo locale (rispettivamente, quello minimo e quello massimo). Nel punto massimo (minimo), il valore della funzione è maggiore (più piccolo) dei valori di tutti i punti adiacenti. La definizione è tratta da Wikipedia (tradotta dal russo).
Per disegnare i canali abbiamo bisogno di estremi locali. Mostriamolo graficamente senza entrare nel merito delle formule matematiche. Nella figura 1 in basso, ci sono tre estremi locali contrassegnati con i livelli di prezzo in rosso. I punti rettangolari mostrano due massimi e un minimo:
Figura 1. Esempi di estremi locali
Non tutti gli estremi esistenti sono contrassegnati sul grafico, ma solo quelli più significativi. Per una candela o un grafico a barre è conveniente usare il termine "frattale" per definire gli estremi, ovvero quando diverse barre adiacenti a sinistra e a destra sono strettamente discendenti o ascendenti (vedi fig.1).
Poiché il nostro scopo non è quello di creare un canale disegnato automaticamente, la posizione degli estremi verrà impostata come mostrato in fig.1, in base alla posizione sugli assi di tempo e sui prezzi. A questo proposito, le più adatte sono le etichette dei prezzi, ovvero gli oggetti grafici speciali del client terminal MetaTrader 5. Un'etichetta di prezzo ha le proprietà delle coordinate di tempo e prezzo, il che consente di identificare definitivamente un punto estremo su un grafico.
L'oggetto per la memorizzazione degli estremi è la classe TExtremum
La prima cosa che dobbiamo fare è sviluppare una classe contenitore per la conservazione degli estremi e una classe per manipolare un gruppo di estremi. Dato che utilizzeremo il più possibile le classi standard incluse nel terminale, la classe TExtremum verrà ereditata dalla classe standard CObject. La descrizione della nostra classe è riportata di seguito:
class TExtremum : public CObject { private: datetime extr_datetime; // data/time in an extremum point double extr_price; // price in an extremum point protected: virtual int Compare(const CObject* _node, int _mode = 0) const; public: void TExtremum(); // constructor void ~TExtremum(); // destructor void SetExtremum(datetime _time, double _price); // change date/time and price in an extremum point void SetDateTime(datetime _time); // change date/time in an extremum point void SetPrice(double _price); // change price in an extremum point public: datetime GetDateTime() const; // get date/time in an extremum point double GetPrice() const; // get price in an extremum point public: virtual bool SaveExtremum(string _dt_name, string _p_name); // save extremum virtual bool LoadExtremum(string _dt_name, string _p_name); // load extremum virtual bool DeleteExtremum(string _dt_name, string _p_name);// delete extremum };
La maggior parte dei metodi sono irrilevanti e non vale la pena prestare attenzione alla loro implementazione. La cosa su cui dovremmo soffermarci è il metodo TExtremum::Compare. Questo metodo viene dichiarato nella classe CObject ed è utilizzato per la suddivisione all'interno di un elenco. L’abbiamo implementato in questo modo:
//--------------------------------------------------------------------- // Comparing two extremums by time: //--------------------------------------------------------------------- int TExtremum::Compare(const CObject* _node, int _mode = 0) const { datetime temp = ((TExtremum* )_node).GetDateTime(); datetime curr = GetDateTime(); if(curr > temp) { return(_mode > 0 ? 1 : -1); } else if(curr < temp) { return(_mode > 0 ? -1 : 1); } return(0); }
Il parametro _mode qui è destinato all'impostazione di una direzione di suddivisione. Se maggiore di zero, l'ordinamento sarà diretto (ascendente), altrimenti sarà inverso (discendente).
Inoltre, ci sono due metodi destinati al salvatagio/caricamento dell’estremo. Disponiamo il nostro estremo in variabili globali. Ecco questi metodi:
//--------------------------------------------------------------------- // Save extremum (date/time): //--------------------------------------------------------------------- bool TExtremum::SaveExtremum(string _dt_name, string _p_name) { datetime dt_result = GlobalVariableSet(_dt_name, (double)extr_datetime); datetime p_result = GlobalVariableSet(_p_name, (double) extr_price); if(dt_result != 0 && p_result != 0) { return(true); } return(false); } //--------------------------------------------------------------------- // Load extremum (date/time): //--------------------------------------------------------------------- bool TExtremum::LoadExtremum(string _dt_name, string _p_name) { double dt_temp, p_temp; bool result = GlobalVariableGet(_dt_name, dt_temp); result &= GlobalVariableGet(_p_name, p_temp); if(result != false) { extr_datetime = (datetime)dt_temp; extr_price = p_temp; return(true); } return(false); }
Due metodi di lettura/scrittura in variabili globali, TExtremum::LoadExtremum e TExtremum::SaveExtremum, restituiscono 'true' in caso di esecuzione riuscita.
Manipolare l'elenco degli estremi: la classe TExtremumList
Poiché abbiamo bisogno sia di memorizzare sia di ordinare gli estremi in base al tempo, dovremo ereditare la classe TExtremumList dalla classe standard CList. Con questa eredità otteniamo un manipolatore universale di estremi senza limiti sul loro numero e tipo. Ciò consente un'ulteriore espansione del numero di canali che vengono disegnati. Ad esempio, possiamo aggiungere il disegno del canale in caso di regressione non lineare da parte di più estremi.
La descrizione di questa classe è riportata di seguito:
class TExtremumList : public CList { private: string channel_prefix; // channel name (prefix) ENUM_TIMEFRAMES chart_timeframe; // current timeframe string chart_symbol; // work symbols of the chart protected: string MakeDTimeName(int _nmb); // get name for saving/reading data/time of an extremum string MakePriceName(int _nmb); // get name for saving/reading price of an extremum public: void TExtremumList(); // конструктор void ~TExtremumList(); // деструктор void SetChannelParams(string _pref, string _symbol = NULL, ENUM_TIMEFRAMES _curr_tf = PERIOD_CURRENT); void AddExtremum(datetime _time, double _price); void DeleteAllExtremum(); void SaveExtremumList(); void LoadExtremumList(); int FindExtremum(datetime _dt); // search extremum by specified time public: datetime GetDateTime(int _index); double GetPrice(int _index); };
Il metodo principale di classe è TExtremumList::AddExtremum. È destinato all'aggiunta di un nuovo estremo all'elenco. La suddivisione degli estremi nell'elenco in base al point time dell'estremo viene eseguito dopo l'aggiunta. Il codice di questo metodo è riportato di seguito:
void TExtremumList::AddExtremum(datetime _time, double _price) { // Create extremum: TExtremum* extr = new TExtremum(); extr.SetExtremum(_time, _price); // Add it in the list: Add(extr); // Sort: Sort(1); }
Qui vengono utilizzati i seguenti metodi della classe base: CList::Add, per aggiungere un nuovo elemento all'elenco, e CList::Sort, per suddividere gli elementi nell'elenco. Il metodo TExtremum::Compare viene utilizzato in CList::Sort.
Diamo un'occhiata al metodo di ricerca di un estremo con il tempo dato nell'elenco TExtremumList::FindExtremum. Il codice del metodo è riportato di seguito:
int TExtremumList::FindExtremum(datetime _dt) { int k = 0; TExtremum* extr = (TExtremum*)(GetFirstNode()); while(extr != NULL) { if(extr.GetDateTime() == _dt) { return(k); } extr = (TExtremum*)(GetNextNode()); } return(-1); // extremum not found }
Qui vengono utilizzati i seguenti metodi della classe base: CList::GetFirstNode, per ottenere il primo elemento dell'elenco (se l'elenco è vuoto, restituisce un puntatore zero), e CList::GetNextNode, per ottenere l'elemento successivo dell'elenco (se non è presente alcun elemento successivo e l'elenco è finito, viene restituito un puntatore zero).
Nota:
C'è un puntatore a un elemento corrente nei dati interni dell'elenco delle classi CList. Questo puntatore viene modificato quando si chiamano i metodi di spostamento nell'elenco (CList::GetFirstNode, CList::GetNextNode, CList::GetPrevNode, ecc.). Se nessuno di questi metodi è stato chiamato prima, il puntatore a un elemento corrente indicherà il primo.
Nel caso in cui si riesca a trovare un estremo con un dato tempo, useremo il metodo TExtremumList::FindExtremum, indice dell'elemento trovato. Se non esiste un elemento di questo tipo, restituirà -1.
I metodi TExtremum::MakeDTimeName e TExtremum::MakePriceName sono ausiliari. Sono destinati a ottenere i nomi delle variabili globali che vengono utilizzate durante il salvataggio e la lettura degli estremi. Questi metodi hanno la seguente implementazione:
string TExtremumList::MakeDTimeName(int _nmb) { string name; StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_DTime_Extr", _nmb); return(name); } string TExtremumList::MakePriceName( int _nmb ) { string name; StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_Price_DTime_Extr", _nmb); return(name); }
Un esempio di nome ottenuto: "MainChannel_EURUSD_5_DTime_Extr1". Questo nome corrisponde a un punto estremo temporaneo del canale MainChannel (nome convenzionale), al simbolo EURUSD, all’intervallo di tempo 5M e all'estremo numero 1. Il numero di un estremo viene assegnato in ordine ascendente del suo tempo, a partire da 1. In pratica, è l'indice spostato su 1 in un elenco ordinato ascendente.
Un esempio di valore di tre estremi salvati nel terminale è mostrato nella figura seguente:
Figura 2. Gli estremi disposti nelle variabili globali
Le classi descritte prima sono allegate all'articolo nel file ExtremumClasses.mqh.
Indicatore per l'impostazione manuale degli estremi: ExtremumHandSet
Bene, abbiamo tutto il necessario per sviluppare il primo indicatore. Per usarlo, imposteremo la posizione degli estremi sulla modalità manuale. Il codice dell'indicatore è allegato all'articolo nel file ExtremumHandSet.MQ5. Analizziamo il suo disegno in dettaglio.
Prima di tutto, immaginiamo visivamente ciò che vogliamo vedere sullo schermo:
Figura 3. L'indicatore di impostazione degli estremi
Utilizzando le etichette dei prezzi a sinistra, impostiamo le posizioni degli estremi sugli assi del tempo e del prezzo del grafico. L'indicatore deve determinare la posizione di tali etichette sul grafico, visualizzare i punti temporanei dell’estremo sullo schermo e salvarli nelle variabili globali del client terminal nel formato descritto prima. Inoltre, l'indicatore deve tenere traccia dello spostamento delle etichette dei prezzi sul grafico e correggere i punti temporanei dell’estremo caricati.
Il monitoraggio dello spostamento delle etichette dei prezzi sul grafico verrà eseguito una volta al secondo. Ciò consentirà al sistema di essere indipendente dall'arrivo di quotazioni e giorni lavorativi/non lavorativi.
Innanzitutto, colleghiamo le librerie richieste:
//--------------------------------------------------------------------- // Included libraries: //--------------------------------------------------------------------- #include <TextDisplay.mqh> #include <ExtremumClasses.mqh>
La prima libreria contiene le classi per l'organizzazione della visualizzazione delle informazioni di testo sullo schermo (vedi l'articolo "Creare un proprio Market Watch usando le classi di Libreria standard"). Usandolo, mostreremo i valori dei punti temporanei dell’estremo.
Poi aggiungiamo i parametri di input dell'indicatore (qui sono descritti solo i principali):
input string PrefixString = "MainChannel"; //--------------------------------------------------------------------- input color ExtremumPointColor = Yellow; //--------------------------------------------------------------------- input bool ShowInfo = true;
Il primo parametro, PrefixString, imposta un prefisso utilizzato per comporre i nomi delle variabili globali durante la scrittura/lettura di un estremo. Dà anche la possibilità di utilizzare diversi indicatori di questo tipo su un singolo grafico. L'unica cosa da fare è impostare dei prefissi diversi per loro.
Il parametro ExtremumPointColor imposta un colore per le etichette dei prezzi a sinistra che determinano la posizione degli estremi. Le etichette dei prezzi devono essere di un colore specificato. Questa conformità viene verificata nell'indicatore. Le etichette con parametri diversi vengono ignorate.
Il parametro ShowInfo controlla la visualizzazione delle informazioni di testo sui punti dell’estremo specificati sullo schermo.
Adesso creeremo gli oggetti per la visualizzazione delle informazioni e la manipolazione degli estremi:
TableDisplay TitlesDisplay; // displaying information on the screen //--------------------------------------------------------------------- TExtremumList* PrevExtr_List; // list of previous extremums TExtremumList* CurrExtr_List; // list of current extremums TExtremumList* NewExtr_List; // list of new extremums
Questi oggetti vengono inizializzati nel modo seguente:
PrevExtr_List = new TExtremumList(); PrevExtr_List.SetChannelParams(PrefixString, Symbol(), Period()); PrevExtr_List.LoadExtremumList(); CurrExtr_List = PrevExtr_List; NewExtr_List = new TExtremumList(); NewExtr_List.SetChannelParams(PrefixString, Symbol(), Period());
Nell'elenco PrevExtr_List carichiamo gli estremi dalle variabili globali usando il metodo TExtremumList::LoadExtremumList. Questo elenco memorizzerà gli estremi per confrontarli con i nuovi, i quali verranno letti da un grafico quando si trascinano le etichette dei prezzi sullo schermo.
L'elenco CurrExtr_List viene utilizzato come corrente e memorizza gli estremi correnti. Visto che all'inizio abbiamo solo gli estremi letti dalle variabili globali, questi verranno interpretati come reali.
Nell'elenco NewExtr_List scriveremo i nuovi estremi trovati sul grafico.
Diamo un'occhiata alle principali funzioni utilizzate nell'indicatore. La prima funzione, FindExtremumPoints, viene utilizzata per leggere e controllare i parametri delle etichette dei prezzi che determinano la posizione degli estremi:
bool FindExtremumPoints(long _chart_id) { string name; // 1. Search for the total number of objects with specified parameters and write them to the list: int total_objects = ObjectsTotal(_chart_id, -1, OBJ_ARROW_LEFT_PRICE); if(total_objects <= 0) { return(false); } NewExtr_List.Clear(); for(int i = 0; i < total_objects; i++) { name = ObjectName(_chart_id, i, -1, OBJ_ARROW_LEFT_PRICE); if( IsGraphicObjectGood(_chart_id, name, OBJ_ARROW_LEFT_PRICE, ExtremumPointColor) == true) { NewExtr_List.AddExtremum(ObjectGetInteger( _chart_id, name, OBJPROP_TIME), ObjectGetDouble(_chart_id, name, OBJPROP_PRICE)); } } // 2. If three extremums are found, we can try to draw a channel: if(NewExtr_List.Total() == 3) { // Save the list of new extremums: NewExtr_List.SaveExtremumList(); return(true); } NewExtr_List.Clear(); return(false); }
Prima di tutto, l'elenco NewExtr_List viene cancellato chiamando il metodo TExtremumList::Clear, quindi vengono aggiunti tutti i punti dell’estremo trovati, i quali possiedono i parametri specificati. Se il numero di punti trovati equivale a tre, l'elenco verrà salvato nelle variabili globali e la funzione restituisce 'true'.
L'altra funzione, CheakExtremumMoving, traccia lo spostamento dei punti dell'estremo sul grafico. Se almeno un punto viene spostato lungo l'asse temporale del grafico, questa funzione restituirà 'true'.
Il suo codice è riportato di seguito:
//--------------------------------------------------------------------- // Check whether extremums have been moved on the screen: //--------------------------------------------------------------------- bool CheakExtremumMoving() { if(FindExtremumLines(0) == true) { int count = NewExtr_List.Total(); int index; for(int i = 0; i < count; i++) { index = CurrExtr_List.FindExtremum(NewExtr_List.GetDateTime(i)); // If a new extremum is found: if(index == -1) { PrevExtr_List = CurrExtr_List; CurrExtr_List = NewExtr_List; return(true); } } CurrExtr_List = PrevExtr_List; } return(false); }
Abbiamo considerato il modo di impostare i punti dell’estremo in modalità manuale. Abbiamo un indicatore già pronto che consente di controllare questo processo e scrivere i punti alle variabili globali. Il codice completo dell'indicatore si trova nel file allegato ExtremumHandSet.mq5. Ora possiamo passare alla parte principale, ovvero disegnare un canale.
Disegnare un canale: alcune teorie
Un canale lineare è costituito da due linee parallele che attraversano rigorosamente i punti dell’estremo. Inoltre, una linea deve passare attraverso due punti e l'altra deve passare attraverso quello che rimane parallelo alla prima riga. Lo si può vedere con una semplice immagine:
Figura 4. Disegnare un canale utilizzando tre punti dell'estremo
Come sappiamo dalla geometria, attraverso due punti può essere disegnata solo una linea retta. Questa linea è di colore rosso nella fig.4. Attraversa i due punti che possiedono le seguenti coordinate: (T1, P1) e (T2, P2); i punti sono contrassegnati con le lettere A e B. L'equazione di questa linea è:
(1) P(t) = P1 + (t - T1)*(P2 - P1) / (T2 - T1); P(t) è il prezzo calcolato al momento 't'.
Attraverso il punto C (il terzo estremo), dovremo disegnare un'altra linea retta parallela alla prima. Questa linea è di colore verde nella fig.3. Poiché i punti T1 e T2 sono gli stessi per entrambe le linee, dovremo trovare i valori di P1' e P2' (vedi fig.4).
Prima di andare avanti, dobbiamo fare un'osservazione importante. Il grafico del terminale non visualizza i "buchi" temporali. Ad esempio, i giorni di liberi, quando le quotazioni non arrivano al terminale, dovrebbero essere visualizzati come interruzioni dei prezzi. Ed è un male che non lo siano. Che senso ha guardare un grafico vuoto? Tuttavia, se usiamo il tempo assoluto nell'equazione di prima, otterremo un canale sbagliato.
Fortunatamente, la situazione non è senza speranza. Se cambiamo il tempo assoluto nel numero relativo di una barra, allora saremo in grado di usare quelle coordinate per disegnare un canale, poiché l'enumerazione delle barre non può avere interruzioni (in pratica, è un indice in una matrice di prezzi).
Se andiamo oltre e assumiamo che il punto A nella fig.4 si trova sempre su una coordinata zero (barra zero) dell'asse del tempo, allora la nostra equazione diventerà ancora più semplice. Quindi, T1 = 0, T3 = B3, Т2 = В2. В3 e В2 qui sono i numeri di una barra relativa al punto Т1 (cioè il punto zero). È chiaro che questa ipotesi non porta all'inclinazione della linea. Quindi otteniamo la seguente equazione di una linea retta che attraversa i punti A e B:
(2) P(n) = P1 + n * (P2-P1) / B2, dove P(n) è il prezzo calcolato per una barra che ha il numero 'n'.
Quindi conosciamo i valori P1, P2, P3 e B2, B3. Ora dobbiamo trovare i valori P1' e P2'. Combinando le due equazioni e risolvendole, otteniamo le seguenti formule, attraverso le quali possiamo trovare i valori sconosciuti:
(3) P1' = P3 - B3 * (P2 - P1) / B2
(4) P2' = P2 - P1 + P1'
Quando troviamo il valore P1' e lo sostituiamo alla formula (4), otterremo il valore P2'. Ora abbiamo le basi teoriche per disegnare un canale. Iniziamo a implementarlo.
Disegnare i bordi dei canale: classe TChannelBorderObject
Questa classe è derivata dalla classe standard CChartObjectTrend. Il suo scopo è memorizzare tutti i parametri collegati ai bordi di un canale, nonché disegnare/eliminare le linee di confine e controllare i loro parametri grafici.
La descrizione di questa classe è riportata di seguito:
class TChannelBorderObject : public CChartObjectTrend { // General properties of a border: private: bool is_created; // whether the graphical object is created on the screen long chart_id; // identifier of the chart window int window; // identifier of the subwindow // Parameters of a border line: private: string border_name; // name of the border line color border_color; // color of the border line int border_width; // thickness of the border line ENUM_LINE_STYLE border_style; // style of the border line // Coordinates of a border: private: datetime point_left; // time of the left point (T1) datetime point_right; // time of the right point (T2) double price_left; // price of the left point (P1) double price_right; // price of the right point (P2) public: void TChannelBorderObject(); // constructor void ~TChannelBorderObject(); // destructor bool IsCreated(); // check whether the line is created // Creating/deleting a line: public: bool CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, double _p_left, double _p_right, color _color, int _width, ENUM_LINE_STYLE _style); bool CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, double _p_left, double _p_right); bool CreateBorder(datetime _t_left, datetime _t_right, double _p_left, double _p_right); bool RemoveBorder(); // delete line from the chart // Setting parameters of the line: public: void SetCommonParams(long _chart_id, int _window, string _name); bool SetBorderParams(color _color, int _width, ENUM_LINE_STYLE _style); bool SetBorderColor(color _color); bool SetBorderWidth(int _width); bool SetBorderStyle(ENUM_LINE_STYLE _style); // Getting values on the line: double GetPrice(datetime _dt); // get price value in the specified position of the border line };
Questa classe non ha bisogno di commenti particolari.
Attenzioniamo solo il metodo per ottenere il prezzo del bordo in un punto specificato:
//--------------------------------------------------------------------- // Get price value in the specified position of the border line: //--------------------------------------------------------------------- double TChannelBorderObject::GetPrice(datetime _dt) { // If the graphical object is created: if(is_created == true) { return(ObjectGetValueByTime( chart_id, border_name, _dt)); } return(0.0); }
La funzione del terminale ObjectGetValueByTime viene utilizzata qui; restituisce il valore del prezzo per un tempo specificato. È conveniente utilizzare le possibilità del terminale invece di calcolare il valore utilizzando una formula matematica.
Disegnare un canale: la classe TSlideChannelObject
Questa classe è derivata dalla classe standard CList. Il suo scopo è il seguente:
- memorizzare gli oggetti della classe TChannelBorderObject ed eseguire con essi diverse azioni;
- calcolare i punti per disegnare le linee necessarie per comporre un canale;
- memorizzare e modificare i parametri di un canale;
- ottenere i valori calcolati che descrivono un canale disegnato (la sua altezza, i valori di prezzo sui bordi, ecc.);
Il codice che descrive questa classe è troppo ampio per poter essere mostrato qui nel completo. Chi vuole, può visualizzarlo nel file SlideChannelClasses.mqh allegato all'articolo. Analizziamo alcune delle sue parti principali.
Prima di tutto, sta ottenendo i valori B2 e B3 rispettivamente nei punti T2 e T3 (vedi fig.4). Viene utilizzato il codice seguente:
// Get relative shifts in bars relatively to the extremum points: total_bars = Bars(symbol, time_frame); // total number of bars in history if(total_bars == 0) { return(false); // channel cannot be drawn } double B2 = Bars(symbol, time_frame, point_left, point_right); double B3 = Bars(symbol, time_frame, point_left, point_middle);
Per evitare una situazione di chiamata di barre assenti, utilizziamo la funzione del terminale Barre, la quale restituisce il numero di barre nella cronologia per un simbolo e un periodo specificati. Se l'informazione non è ancora formata, la funzione restituirà un valore zero che si usa per il controllo.
Se la funzione restituisce un valore diverso da zero, possiamo ottenere i valori В2 e В3. Viene eseguito utilizzando la stessa funzione Barre, ma chiamandola nell'altra forma. Impostiamo i limiti di tempo e otteniamo il numero di barre all'interno di questo intervallo. Dato che il nostro bordo sinistro è lo stesso, otteniamo lo spostamento delle barre per i punti Т2 e Т3. Lo spostamento per il punto Т1 è sempre uguale a zero.
Adesso possiamo calcolare tutto il punto delle linee del canale. Ce ne possono essere al massimo nove, poiché il nostro canale visualizzerà (oltre ai bordi superiore e inferiore) la linea centrale e le linee delle zone percentuali attorno ai bordi e alla linea centrale.
Analizziamo la parte principale del calcolo. L'intero calcolo si trova nel metodo TSlideChannelObject::CalcChannel.
// Coefficient of the line inclination: koeff_A = (price_right - price_left) / B2; // Price value on the AB line in the point T3: double P3_AB = price_left + B3 * koeff_A; // Determine the channel type - 2MAX_1MIN или 1MAX_2MIN: if(P3_AB > price_middle) // 2MAX_1MIN { channel_type = CHANNEL_2MAX_1MIN; left_prices[BORDER_UP_INDEX] = price_left; right_prices[BORDER_UP_INDEX] = price_right; left_prices[BORDER_DN_INDEX] = price_middle - B3 * koeff_A; right_prices[BORDER_DN_INDEX] = left_prices[BORDER_DN_INDEX] + (price_right - price_left); } else if(P3_AB < price_middle) // 1MAX_2MIN { channel_type = CHANNEL_1MAX_2MIN; left_prices[BORDER_DN_INDEX] = price_left; right_prices[BORDER_DN_INDEX] = price_right; left_prices[BORDER_UP_INDEX] = price_middle - B3 * koeff_A; right_prices[BORDER_UP_INDEX] = left_prices[BORDER_UP_INDEX] + (price_right - price_left); } else { return( false ); // channel cannot be drawn (all extremums are on the same line) }
left_prices e right_prices ecco gli array che memorizzano le coordinate di prezzo delle nove linee del canale. Le coordinate temporali di tutte le linee del canale sono già note.
Dapprima, determina il coefficiente dell'inclinazione della linea (vedi la formula (2)) koeff_A. Quindi, calcoliamo il valore del prezzo della linea AB nel punto T3 (vedi fig.4). Serve per determinare quale tipo di canale è specificato per il disegno, da due massimi e un minimo o da due minimi e un massimo. Controlliamo quale punto è più alto sull'asse del prezzo, se il punto C o il punto che ha le coordinate (P3', T3). A seconda della loro posizione determiniamo se il canale è del primo o del secondo tipo.
Una volta determinate le coordinate delle due linee principali del canale (superiore e inferiore), calcolare le coordinate delle altre sette linee non sarà difficile da fare. Ad esempio, calcoliamo le coordinate della linea di mezzo utilizzando le coordinate dei bordi superiore e inferiore del canale nel modo seguente:
left_prices[BORDER_MD_INDEX] = (left_prices[BORDER_DN_INDEX] + left_prices[BORDER_UP_INDEX ]) / 2.0; right_prices[BORDER_MD_INDEX] = (right_prices[BORDER_DN_INDEX] + right_prices[BORDER_UP_INDEX]) / 2.0;
Basta prendere il valore medio dai bordi superiore e inferiore del canale.
Indicatore per il disegnare un canale mediante gli estremi specificati: SlideChannel
Bene, abbiamo già la classe per disegnare un canale. Ora scriviamo un indicatore che leggerà i parametri degli estremi dalle variabili globali e disegnerà un canale su un grafico. Avrà il seguente aspetto:
Figura 5. Esempio di un canale disegnato usando gli estremi
Le informazioni sul canale disegnato vengono visualizzate anche qui: la sua larghezza, la distanza in punti dal prezzo corrente ai bordi del canale e nella linea di mezzo.
Colleghiamo le librerie richieste:
#include <TextDisplay.mqh> #include <SlideChannelClasses.mqh>
La prima libreria contiene le classi per l'organizzazione della visualizzazione delle informazioni di testo sullo schermo (vedi l'articolo "Creare un proprio Market Watch usando le classi di Libreria standard"). Usandolo, mostreremo i valori dei punti temporanei dell’estremo.
Quindi aggiungiamo i parametri di input dell'indicatore (qui sono descritti solo i principali):
input string PrefixString = "MainChannel"; //--------------------------------------------------------------------- input ENUM_TIMEFRAMES ExtremumTimeFrame = PERIOD_CURRENT; //--------------------------------------------------------------------- input bool ShowInfo = true;
Il primo parametro, PrefixString, proprio come nell'indicatore ExtremumHandSet, imposta un prefisso utilizzato per comporre il nome delle variabili globali durante la lettura degli estremi. Dà anche la possibilità di utilizzare diversi indicatori di questo tipo su un singolo grafico. L'unica cosa da fare è impostare dei prefissi diversi per loro.
Il parametro ExtremumTimeFrame imposta un intervallo di tempo che verrà usato per leggere i punti dell'estremo dalle variabili globali. È un parametro molto utile. Permette di disegnare canali sincroni su diversi intervalli di tempo. Ad esempio, se si impostano gli estremi da H1, potrai disegnare lo stesso canale sull'intervallo di tempo M5. Per farlo, basta aggiungere il nostro indicatore per disegnare i canali al grafico M5, il quale mostrerà in modo sincrono tutte le modifiche.
Il parametro ShowInfo controlla la visualizzazione delle informazioni di testo sui parametri del canale sullo schermo.
Adesso, crea gli oggetti per visualizzare le informazioni e disegnare il canale:
TableDisplay ChannalDisplay; // displaying of general information about a channel on the screen TableDisplay BordersDisplay; // displaying information about the borders of a channel on the screen //--------------------------------------------------------------------- TSlideChannelObject Channel; // drawing of a channel
L'oggetto per disegnare un canale viene inizializzato nel modo seguente:
Channel.CreateChannel(PrefixString, 0, 0, Symbol(), period_current, curr_left_point, curr_middle_point, curr_right_point, curr_left_price, curr_middle_price, curr_right_price); Channel.SetBorderWidth(BorderWidth ); Channel.SetmiddleWidth(middleLineWidth); Channel.SetUpBorderColor(UpBorderColor); Channel.SetDnBorderColor(DnBorderColor); Channel.SetmiddleColor(middleLineColor ); Channel.ShowBorderZone(ShowBorderPercentageLines); Channel.BorderZonePercentage( PercentageZoneSize); Channel.Showmiddle(ShowmiddleLine); Channel.ShowmiddleZone( ShowmiddlePercentageLines); Channel.middleZonePercentage(PercentagemiddleZoneSize);
Qui, in un primo momento, creiamo un canale chiamando il metodo TSlideChannelObject::CreateChannel, poi impostiamo i parametri richiesti della linea del canale. La sequenza di impostazione non ha importanza, puoi farlo viceversa: imposta i parametri e poi crea il canale.
Il parametro period_current è il periodo utilizzato per la lettura degli estremi dalle variabili globali. Potrebbe essere diverso dal periodo di un grafico corrente.
Diamo un'occhiata alle principali funzioni utilizzate nell'indicatore. La prima funzione, GetExtremums, viene utilizzata per leggere la posizione degli estremi e rinfrescare il canale in base ai valori ottenuti:
void GetExtremums() { double temp; string name; StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr1"); if( GlobalVariableGet(name, temp) != false) { curr_left_point = (datetime)temp; } StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr2"); if( GlobalVariableGet(name, temp) != false) { curr_middle_point = (datetime)temp; } StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr3"); if( GlobalVariableGet(name, temp) != false) { curr_right_point = (datetime)temp; } StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr1"); if( GlobalVariableGet(name, temp) != false) { curr_left_price = temp; } StringConcatenate(name, PrefixString, "_", Symbol( ), "_", period_current, "_Price_Extr2"); if( GlobalVariableGet(name, temp) != false ) { curr_middle_price = temp; } StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr3"); if( GlobalVariableGet(name, temp) != false) { curr_right_price = temp; } // Update the position of channel: Channel.SetExtremums(curr_left_point, curr_middle_point, curr_right_point, curr_left_price, curr_middle_price, curr_right_price); }
Per aggiornare il canale sullo schermo, usiamo il metodo TSlideChannelObject::SetExtremums. Questo metodo ricalcola le coordinate delle linee del canale e ridisegna il canale sullo schermo.
Un esempio di come disegnare un canale su diversi intervalli di tempo è mostrato nel video qui sotto:
La sequenza di indicatori iniziali non ha molta importanza, ma è logico avviare prima l'indicatore ExtremumHandSet, quindi aggiungere tre etichette di prezzo a sinistra di colore giallo (il colore delle etichette è impostato nei parametri dell'indicatore, il colore giallo è impostato per impostazione predefinita) e avviare l'indicatore SlideChannel, il quale disegna il canale dagli estremi specificati.
Affinché un canale venga disegnato in modo sincrono con gli estremi del primo grafico, è necessario impostare l'intervallo di tempo nel parametro ExtremumTimeFrame dell'indicatore SlideChannel ugualmente al grafico in cui sono impostati gli estremi.
Questo è il risultato della separazione della funzione di impostazione dei punti dell’estremo del canale dalla funzione del suo disegno sullo schermo del terminale.
Conclusione
Abbiamo preso in considerazione l'intero ciclo, dall'impostazione della posizione di un canale sullo schermo al suo disegno. Non sembrava essere così complicato, specialmente se si utilizzano le classi standard e OOP.
Ma resta una domanda: come usare i canali per lavorare nel market. Innanzitutto, sono necessari per l'analisi tecnica dello stato attuale di uno strumento finanziario. E in secondo luogo, dopo l'analisi, sono necessari per prendere delle decisioni. I canali possono aiutare molto.
È possibile sviluppare un Expert Advisor semiautomatico che analizzerà i bordi di un canale per l'apertura o la chiusura di una posizione. Può funzionare sia con la rottura di un confine sia tornando indietro. Questo sarà l'argomento del prossimo articolo, The Methods of Working with a Channel - Roll Back and Break Through.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/200





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso