English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Disegnare i canali - Vista interna ed esterna

Disegnare i canali - Vista interna ed esterna

MetaTrader 5Indicatori | 17 dicembre 2021, 14:44
127 0
Dmitriy Skub
Dmitriy Skub

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

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

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

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:

Disegnare un canale utilizzando tre punti dell’estremo

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 utilizzando gli estremi

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

File allegati |
extremumclasses.mqh (12.58 KB)
extremumhandset.mq5 (11.89 KB)
slidechannel.mq5 (13.7 KB)
textdisplay.mqh (15.21 KB)
Creare Expert Advisor multipli sulla base dei modelli di trading Creare Expert Advisor multipli sulla base dei modelli di trading
L'utilizzo di un approccio orientato agli oggetti in MQL5 semplifica enormemente la creazione di Expert Advisor multivaluta/multisistema/multitimeframe. Immagina, il tuo unico EA esegue le operazioni su diverse dozzine di strategie di trading, su tutti gli strumenti disponibili e su tutti i possibili intervalli di tempo! Inoltre, l'EA è facilmente testabile nel tester e per tutte le strategie incluse nella sua composizione possiede uno o più sistemi di gestione del denaro funzionanti.
Calcoli paralleli su MetaTrader 5 Calcoli paralleli su MetaTrader 5
Il tempo ha sempre avuto un grande valore in tutta la storia dell'umanità e noi ci sforziamo di non sprecarlo inutilmente. Questo articolo ti dirà come accelerare il lavoro del tuo Expert Advisor se il tuo computer ha un processore multi-core. Inoltre, l'implementazione del metodo proposto non richiede la conoscenza di altri linguaggi oltre a MQL5.
Gli indicatori dei trend micro, medie e principali Gli indicatori dei trend micro, medie e principali
Lo scopo di questo articolo è indagare le possibilità del trading e dell'analisi sulla base di alcune idee tratte dal libro di James Hyerczyk "Pattern, Price & Time: Using Gann Theory in Trading Systems" sotto forma di indicatori ed Expert Advisor. Senza pretendere di essere esaustivi, qui indagheremo solo il Modello, la prima parte della teoria di Gann.
Progettare e implementare nuovi widget GUI basati sulla classe CChartObject Progettare e implementare nuovi widget GUI basati sulla classe CChartObject
Dopo l’articolo sull’Expert Advisor semiautomatico con interfaccia GUI che ho scritto in precedenza, ho realizzato che sarebbe stato opportuno migliorare l'interfaccia con alcune nuove funzionalità per gli indicatori e gli Expert Advisor più complessi. Dopo aver familiarizzato con le classi della libreria standard MQL5 ho implementato nuovi widget. Questo articolo descrive un processo di progettazione e implementazione di nuovi widget GUI MQL5 che possono essere utilizzati negli indicatori e negli Expert Advisor. I widget presentati nell'articolo sono CChartObjectSpinner, CChartObjectProgressBar e CChartObjectEditTable.