English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Approccio Orientato agli Oggetti per la Creazione di Pannelli Multi-Timeframe e Multi-Valuta

Approccio Orientato agli Oggetti per la Creazione di Pannelli Multi-Timeframe e Multi-Valuta

MetaTrader 5Esempi | 9 dicembre 2021, 14:20
78 0
Marcin Konieczny
Marcin Konieczny

Introduzione

Questo articolo descrive come la programmazione orientata agli oggetti può essere utilizzata per la creazione di pannelli multi-timeframe e multi-valuta per MetaTrader 5. L'obiettivo principale è quello di costruire un pannello universale, che può essere utilizzato per visualizzare diversi tipi di dati, come prezzi, variazioni di prezzo, valori degli indicatori o condizioni di acquisto/vendita personalizzate senza la necessità di modificare il codice del pannello stesso. In questo modo sarà necessaria pochissima codifica per personalizzare il pannello in qualsiasi modo necessario.

La soluzione che descriverò funziona in due modalità:

  1. Modalità multi-timeframe - consente di vedere il contenuto della tabella calcolato sul simbolo corrente, ma su intervalli di tempo diversi;
  2. Modalità multi-valuta - consente di vedere il contenuto della tabella calcolato sull'intervallo di tempo corrente, ma su simboli diversi.

Le seguenti immagini mostrano il pannello in queste due modalità.

Il primo funziona in modalità multi-timeframe e visualizza i seguenti dati:

  1. Prezzo attuale;
  2. Variazione di prezzo della barra corrente;
  3. Il prezzo della barra corrente cambia in percentuale;
  4. Il prezzo della barra corrente cambia come una freccia (su/giù);
  5. Valore dell'indicatore RSI(14);
  6. Valore dell'indicatore RSI(10);
  7. Condizione personalizzata: SMA(20) > prezzo corrente.

Figura 1. Modalità Multi-timeframe

Figura 1. Modalità Multi-timeframe


Il secondo funziona in modalità multi-valuta e mostra:

  1. Prezzo attuale;
  2. Variazione di prezzo della barra corrente;
  3. Il prezzo della barra corrente cambia in percentuale;
  4. Il prezzo della barra corrente cambia come una freccia;
  5. Valore dell'indicatore RSI(10);
  6. Valore dell'indicatore RSI(14);
  7. Condizione personalizzata: SMA(20) > prezzo corrente.

Figura 2. Modalità valutaMultMult

Figure 2. Modalità Mult-valuta


1. Implementazione

Nel seguente diagramma di classe viene descritta la progettazione dell'implementazione del pannello.

Figura 3. Diagramma di classe del pannello

Figura 3. Diagramma di classe del pannello


Permettetemi di descrivere gli elementi del diagramma:

  1. CTable. Classe principale del pannello. È responsabile del disegno del pannello e della gestione dei suoi componenti.
  2. SpyAgent. È un indicatore responsabile dello "spionaggio" di altri simboli (strumenti). Ogni agente viene creato e inviato a un simbolo diverso. L'agente reagisce all'evento OnCalculate quando un nuovo tick arriva nel grafico dei simboli e invia CHARTEVENT_CUSTOM evento per informare l'oggetto CTable che si deve aggiornare. L'intera idea alla base di questo approccio si basa sull'articolo "The Implementation of a Multi-currency Mode in MetaTrader 5". Puoi trovare tutti i dettagli tecnici nell’articolo.
  3. CRow. Classe base per tutti gli indicatori e le condizioni utilizzati per creare il pannello. Estendendo questa classe è possibile creare tutti i componenti necessari del pannello.
  4. CPriceRow. Classe semplice che estende CRow, che viene utilizzata per visualizzare il prezzo dell’offerta corrente.
  5. CPriceChangeRow. La classe che estende CRow, utilizzata per visualizzare la variazione di prezzo della barra corrente. Può mostrare variazioni di prezzo, variazioni percentuali o frecce.
  6. CRSIRow. La classe che estende CRow, utilizzato per visualizzare il valore dell'indicatore RSI corrente.
  7. CPriceMARow. Classe che estende CRow, mostrando una condizione personalizzata: SMA > prezzo corrente

Le classi CTable e CRow e l'indicatore SpyAgent sono le parti principali del pannello. CPriceRow, CPriceChangeRow, CRSIRow e CPriceMARow sono contenuti effettivi del pannello. La classe CRow è progettata per essere estesa da molte nuove classi al fine di ottenere il risultato desiderato. Le quattro classi derivate presentate sono solo semplici esempi di cosa si può fare e come.


2. SpyAgent

Inizieremo con l'indicatore SpyAgent. Viene utilizzato solo in modalità multi-valuta ed è necessario aggiornare correttamente il pannello, quando un nuovo tick arriva su altri grafici. Non entrerò molto nei dettagli di questo concetto. Sono descritti nell'articolo "The Implementation of a Multi-currency Mode in MetaTrader 5".

L'indicatore SpyAgent viene eseguito sul grafico del simbolo specificato e invia due eventi: evento di inizializzazione e nuovo evento tick. Entrambi gli eventi sono di tipo CHARTEVENT_CUSTOM. Per gestire questi eventi dobbiamo usare il gestore OnChartEvent(...) (verrà mostrato più avanti nell'articolo).

Diamo un'occhiata al codice di SpyAgent:

//+------------------------------------------------------------------+
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // chart id
input ushort custom_event_id=0; // event id
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // sends initialization event
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // sends new tick event

   return(rates_total);
  }
È abbastanza semplice. L'unica cosa che fa è ricevere nuovi tick e inviare gli eventi CHARTEVENT_CUSTOM.


3. CTable

CTable è la classe principale del pannello. Memorizza le informazioni sulle impostazioni del pannello e gestisce i suoi componenti. Aggiorna (ridisegna) il pannello, quando necessario.

Diamo un'occhiata all’informativa di CTable:

//+------------------------------------------------------------------+
//| CTable class                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // distance from right border of the chart
   int               yDistance;    // distance from top of the chart
   int               cellHeight;   // table cell height
   int               cellWidth;    // table cell width
   string            font;         // font name
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // list of row objects
   bool              tfMode;       // is in multi-timeframe mode?

   ENUM_TIMEFRAMES   timeframes[]; // array of timeframes for multi-timeframe mode
   string            symbols[];    // array of currency pairs for multi-currency mode

   //--- private methods
   //--- sets default parameters of the table
   void              Init();
   //--- draws text label in the specified table cell
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- returns textual representation of given timeframe
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- multi-timeframe mode constructor
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- multi-currency mode constructor
                     CTable(string &symb[]);
   //--- destructor
                    ~CTable();
   //--- redraws table
   void              Update();
   //--- methods for setting table parameters
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- appends CRow object to the of the table
   void              AddRow(CRow *row);
  };

Come puoi vedere, tutti i componenti del pannello (righe) sono memorizzati come un elenco di puntatori CRow, quindi ogni componente che vogliamo aggiungere al pannello deve estendere la classe CRow. CRow può essere visto come un contratto tra il pannello e i suoi componenti. CTable non contiene alcun codice per il calcolo delle sue celle. È una responsabilità delle classi che estendono CRow. CTable è solo una struttura per contenere i componenti CRow e ridisegnarli quando necessario.

Esaminiamo i metodi di CTable. La classe dispone di due constructor. Il primo viene utilizzato per la modalità multi-timeframe ed è abbastanza semplice. Dobbiamo solo fornire una serie di timeframe che vogliamo visualizzare.

//+------------------------------------------------------------------+
//| Multi-timeframe mode constructor                                 |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- copy all timeframes to own array
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;
   
//--- fill symbols array with current chart symbol
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- set default parameters
   Init();
  }

Il secondo costruttore viene utilizzato per la modalità multi-valuta e accetta un array di simboli (strumenti). Questo invia anche SpyAgents. Li associa uno per uno ai grafici appropriati.

//+------------------------------------------------------------------+
//| Multi-currency mode constructor                                  |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- copy all symbols to own array
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;
   
//--- fill timeframe array with current timeframe
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- set default parameters
   Init();

//--- send SpyAgents to every requested symbol
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // don't send SpyAgent to own chart
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("Error in setting of SpyAgent on "+symbols[x]);
            return;
           }
  }

Il metodo Init crea l'elenco di righe (come oggetto CList - CList è un elenco dinamico di tipiCObject e imposta i valori predefiniti per le variabili interne CTable (carattere, dimensione del carattere, colore, dimensione della cella e distanza dall'angolo superiore destro del chart).

//+------------------------------------------------------------------+
//| Sets default parameters of the table                             |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- create list for storing row objects
   rowList=new CList;

//--- set defaults
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

Il destructor è abbastanza semplice. Elimina l'elenco delle righe ed elimina tutti gli oggetti del chart (etichette) creati dal pannello.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- remove all text labels from the chart (all object names starting with nameBase prefix)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- delete list of rows and free memory
   delete(rowList);
  }

Il metodo AddRow aggiunge la nuova riga all'elenco di righe. Tieni presente che rowList è un oggetto CList che viene ridimensionato automaticamente. Questo metodo chiama anche il metodo Init per ogni oggetto CRow aggiunto. È necessario consentire all'oggetto di inizializzare correttamente le sue variabili interne. Ad esempio, può utilizzare la chiamata Init per creare indicatori o handle di file.

//+------------------------------------------------------------------+
//| Appends new row to the end of the table                          |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

Il metodo Update è un po' più complicato. Viene utilizzato per ridisegnare il pannello.

Fondamentalmente, si compone di tre parti, che sono:

  • Disegno della prima colonna (i nomi delle righe)
  • Disegno della prima riga (i nomi dei timeframe o dei simboli a seconda della modalità selezionata)
  • Disegno delle celle interne (valori dei componenti)

Vale la pena notare che chiediamo a ogni componente di calcolare il proprio valore in base al simbolo e al timeframe forniti. Lasciamo anche che sia il componente a decidere, quale carattere e colore devono essere utilizzati.

//+------------------------------------------------------------------+
//| Redraws the table                                                |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // number of rows
   int columns;              // number of columns

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- draw first column (names of rows)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- note: we ask row object to return its name
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- draws first row (names of timeframes or currency pairs)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- draws inside table cells
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- in multi-timeframe mode use current symbol and different timeframes
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- in multi-currency mode use current timeframe and different symbols
            tf=Period();
            symbol=symbols[x];
           }

         //--- note: we ask row object to return its font, 
         //--- color and current calculated value for given timeframe and symbol
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- forces chart to redraw
   ChartRedraw();
  }

Il metodo DrawLabel viene utilizzato per disegnare etichette di testo nella cella specifica del pannello. In primo luogo, controlla se esiste già un'etichetta per questa cella. In caso contrario, ne crea una nuova.

Quindi, imposta tutte le proprietà dell'etichetta necessarie e il relativo testo.

//+------------------------------------------------------------------+
//| Draws text label in the specified cell of the table              |
//+------------------------------------------------------------------+  
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- create unique name for this cell
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- create label
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- set label properties
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- set label text
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

Altri metodi non saranno presentati qui, perché sono molto semplici e meno importanti. Il codice completo può essere scaricato in fondo all'articolo.


4. Estensione di CRow

CRow è una classe base per tutti i componenti, che può essere utilizzata dal pannello.

Diamo un'occhiata al codice della classe CRow:

//+------------------------------------------------------------------+
//| CRow class                                                       |
//+------------------------------------------------------------------+
//| Base class for creating custom table rows                        |
//| one or more methods of CRow should be overriden                  |
//| when creating own table rows                                     |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- default initialization method
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- default method for obtaining string value to display in the table cell
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- default method for obtaining color for table cell
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- default method for obtaining row name
   virtual string GetName() { return("-"); }

   //--- default method for obtaining font for table cell
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

Estende CObject, poiché solo CObjects può essere memorizzato in una struttura CList. Ha cinque metodi, che sono quasi vuoti. Per essere più precisi, la maggior parte di essi restituisce solo valori predefiniti. Questi metodi sono progettati per essere sovrascritti durante l'estensione di CRow. Non abbiamo bisogno di ignorarli tutti, solo quelli che vogliamo.

Ad esempio, creiamo il componente del pannello più semplice possibile: il componente del prezzo di offerta corrente. Può essere utilizzato in modalità multi-valuta per visualizzare i prezzi correnti di vari strumenti.

Per raggiungere questo obiettivo creiamo una classe CPriceRow, che assomiglia a questa:

//+------------------------------------------------------------------+
//| CPriceRow class                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

  };
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- gets current price
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

I metodi che abbiamo scelto di ignorare qui sono GetValue e GetName. GetName restituisce semplicemente il nome di questa riga, che verrà visualizzato nella prima colonna del pannello. GetValue ottiene l'ultimo tick sul simbolo specificato e restituisce l'ultimo prezzo di offerta. Questo è tutto ciò di cui abbiamo bisogno.

Questo è stato abbastanza semplice. Facciamo qualcosa di diverso. Ora costruiremo un componente, che mostra il valore RSI corrente.

Il codice è simile al precedente:

//+------------------------------------------------------------------+
//| CRSIRow class                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // RSI period
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of RSI handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CRSIRow(int period);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets RSI handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- gets RSI indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets current RSI value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Abbiamo alcuni nuovi metodi qui. Il costruttore consente di fornire il periodo RSI e memorizzarlo come variabile membro. Il metodo Init viene utilizzato per la creazione di handle di indicatori RSI. Questi handle vengono memorizzati nell’array handles[]. Il metodo GetValue copia l'ultimo valore dal buffer RSI e lo restituisce. Il metodo GetHandle privato viene utilizzato per trovare l'handle dell'indicatore corretto nell'array handles[]. GetName è autoesplicativo.

Come possiamo vedere, costruire componenti del pannello è abbastanza facile. Allo stesso modo possiamo creare componenti per quasi tutte le condizioni personalizzate. Non deve essere il valore dell'indicatore. Di seguito presento una condizione personalizzata basata su SMA. Controlla se il prezzo corrente è superiore alla media mobile e visualizza "Sì" o "No".

//+------------------------------------------------------------------+
//| CPriceMARow class                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // period of moving average
   int               maShift;  // shift of moving average
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA or LWMA
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of MA handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- overrides default GetValue(..) method of CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // overrides default GetName() method CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| CPriceMARow class constructor                                    |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets MA handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method of CRow                    |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- obtains MA indicator handle
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- gets the last MA value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- gets the last price
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- checking the condition: price > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method of CRow                       |
//+------------------------------------------------------------------+
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Il codice è più lungo, perché la Media Mobile ha tre parametri: period, shift e type. GetName è un po’ più complicato in quanto crea il nome in base al tipo e al punto MA. GetValue funziona quasi allo stesso modo di CRSIRow, ma invece di restituire il valore dell'indicatore restituisce "Sì" se il prezzo è superiore allo SMA o "No" se è inferiore.

L'ultimo esempio è un po' più complesso. È la classe CPriceChangeRow, che mostra la variazione di prezzo della barra corrente. Funziona in tre modalità:

  • Visualizzazione delle frecce (verde su o rosso verso il basso);
  • Visualizzazione della variazione di prezzo come valore (verde o rosso);
  • Visualizzazione della variazione di prezzo in percentuale (verde o rosso).

Il codice è simile al seguente:

//+------------------------------------------------------------------+
//| CPriceChangeRow class                                            |
//+------------------------------------------------------------------+
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- constructor
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default GetFont() method from CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetColor(..) method from CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| CPriceChangeRow class constructor                                |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Overrides default GetFont() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- we use Wingdings font to draw arrows (up/down)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- current bar price change
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // returns up arrow code
      if(change < 0) return(CharToString(234)); // returns down arrow code
      return(" ");
        }else{
      if(percentChange)
        {
         //--- calculates percent change
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Overrides default GetColor(..) method from CRow                  |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

Il costruttore ha due parametri. Per prima cosa si decide se mostrare le frecce. Se è vero, il secondo parametro viene scartato. Se è falso, il secondo parametro decide se mostrare le variazioni percentuali o solo le variazioni di prezzo.

Per questa classe, ho deciso di ignorare quattro metodi di CRow: GetName, GetValue, GetColor e GetFont. GetName è il più semplice e restituisce solo il nome. GetFont viene utilizzato, perché dà la possibilità di visualizzare frecce o altri caratteri dal carattere Wingdings. GetColor restituisce il colore lime quando il prezzo aumenta e il rosso quando scende. Il colore bianco viene restituito quando rimane in posizione o in caso di errori. GetValue ottiene i prezzi di apertura e chiusura dell'ultima barra, calcola la differenza e la restituisce. Nella modalità arrow restituisce i codici dei caratteri Wingdings delle frecce su e giù.


5. Come usare il tutto

Per utilizzare il pannello dobbiamo creare un nuovo indicatore. Lo chiameremo TableSample.

Gli eventi che dobbiamo gestire sono:

Abbiamo anche bisogno di un puntatore all'oggetto CTable, che verrà creato dinamicamente in OnInit(). Prima di tutto, dobbiamo decidere, quale modalità useremo (multi-timeframe o multi-valuta). L'esempio di codice riportato di seguito mostra la modalità multi-valuta, ma tutto ciò che è necessario per la modalità multi-timeframe è anche qui nei commenti. Per la modalità multi-valuta è necessario creare un array di simboli e passarlo al constructor CTable. Per la modalità multi-timeframe creeremo un array di intervalli di tempo e lo passeremo al secondo constructor CTable.

Dopodiché dobbiamo creare tutti i componenti necessari e aggiungerli al pannello utilizzando il metodo AddRow. Facoltativamente, i parametri del pannello possono essere regolati. Dopotutto, dobbiamo disegnare il pannello per la prima volta, quindi chiamiamo Update alla fine di OnInit(). OnDeinit è semplice. L'unica cosa che fa è eliminare l'oggetto CTable, ciò che causa la chiamata del destructor CTable.

OnCalculate(...) e OnChartEvent(...) sono identici. Chiamano solo il metodo Update. OnChartEvent(...) è necessario solo se il pannello funziona in modalità multi-valuta. In questa modalità gestisce gli eventi generati da SpyAgents. In modalità multi-timeframe è necessario solo OnCalculate(...), perché dobbiamo monitorare solo il simbolo del chart corrente.

//+------------------------------------------------------------------+
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // pointer to CTable object
//+------------------------------------------------------------------+
//| Indicator initialization function                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- timeframes used in table (in multi-timeframe mode)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- symbols used in table (in multi-currency mode)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- CTable object creation 
//   table = new CTable(timeframes); // multi-timeframe mode
   table=new CTable(symbols); // multi-currency mode

//--- adding rows to the table
   table.AddRow(new CPriceRow());               // shows current price
   table.AddRow(new CPriceChangeRow(false));     // shows change of price in the last bar
   table.AddRow(new CPriceChangeRow(false,true)); // shows percent change of price in the last bar
   table.AddRow(new CPriceChangeRow(true));      // shows change of price as arrows
   table.AddRow(new CRSIRow(14));                // shows RSI(14)
   table.AddRow(new CRSIRow(10));                // shows RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // shows if SMA(20) > current price

//--- setting table parameters
   table.SetFont("Arial",10,clrYellow);  // font, size, color
   table.SetCellSize(60, 20);           // width, height
   table.SetDistance(10, 10);           // distance from upper right chart corner

   table.Update(); // forces table to redraw

   return(0);
  }
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- calls table destructor and frees memory
   delete(table);
  }
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- update table: recalculate/repaint
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnChartEvent handler                                             |
//| Handles CHARTEVENT_CUSTOM events sent by SpyAgent indicators     |
//| nedeed only in multi-currency mode!                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // update table: recalculate/repaint
  }
//+------------------------------------------------------------------+

Dopo aver allegato questo indicatore al chart, inizia ad aggiornarsi e possiamo finalmente vedere il pannello funzionante.


6. Installazione

Tutti i file devono essere compilati. SpyAgent e TableSample sono indicatori e devono essere copiati in terminal_data_folder\MQL5\Indicators. I file rimanenti sono file include e devono essere inseriti all'interno di terminal_data_folder\MQL5\Include. Per eseguire il pannello, collegare l'indicatore TableSample a qualsiasi chart. Non è necessario allegare SpyAgent. Verranno avviati automaticamente.


Conclusione

L'articolo fornisce un'implementazione orientata agli oggetti del pannello multi-timeframe e multi-valuta per MetaTrader 5. Mostra come ottenere un design, che è facilmente estensibile e consente di costruire pannelli personalizzati con poco sforzo.

Tutto il codice presentato in questo articolo può essere scaricato di seguito.


Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/357

Sistemi di Trading Semplici che Utilizzano Indicatori Semaforici Sistemi di Trading Semplici che Utilizzano Indicatori Semaforici
Se esaminiamo nel dettaglio qualsiasi sistema di trading complesso, vedremo che si basa su una serie di semplici segnali di trading. Pertanto, non è necessario che gli sviluppatori alle prime armi inizino immediatamente a scrivere algoritmi complessi. Questo articolo fornisce un esempio di un sistema di trading che utilizza indicatori semaforici per eseguire operazioni.
Le Basi della Programmazione Orientata agli Oggetti Le Basi della Programmazione Orientata agli Oggetti
Non hai bisogno di sapere cosa sono il polimorfismo, l'incapsulamento, ecc. per usare la programmazione orientata agli oggetti (OOP) ... puoi semplicemente usare queste funzionalità. Questo articolo descrive le basi dell'OOP attraverso degli esempi pratici.
Crea il Tuo Robot di Trading in 6 Passaggi! Crea il Tuo Robot di Trading in 6 Passaggi!
Se non sai come vengono costruite le classi di trading e hai paura delle parole "Programmazione Orientata agli Oggetti", allora questo articolo fa per te. In effetti, non è necessario conoscere i dettagli per scrivere il proprio modulo di segnali di trading. Basta seguire alcune semplici regole. Tutto il resto verrà fatto da MQL5 Wizard e otterrai un robot di trading pronto all'uso!
Velocizza i Calcoli con il Cloud Network MQL5 Velocizza i Calcoli con il Cloud Network MQL5
Quanti core hai sul tuo computer di casa? Quanti computer puoi utilizzare per ottimizzare una strategia di trading? Qui mostriamo come utilizzare il Cloud Network MQL5 per accelerare i calcoli ricevendo la potenza di calcolo in tutto il mondo con un click del mouse. La frase "Il tempo è denaro" diventa ancora più attuale con il passare degli anni e non possiamo permetterci di aspettare calcoli importanti per decine di ore o addirittura giorni.