English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
Indicatori multipli su un grafico (Parte 02): Primi esperimenti

Indicatori multipli su un grafico (Parte 02): Primi esperimenti

MetaTrader 5Esempi | 19 luglio 2022, 16:18
171 0
Daniel Jose
Daniel Jose

Introduzione

Nel precedente articolo "Indicatori multipli su un grafico" ho presentato la logica e le basi relative a come utilizzare più indicatori su un grafico, senza riempire lo schermo con troppi dettagli diversi. L'unico scopo di quell'articolo era presentare il sistema stesso, mostrare come creare database e come sfruttare tali database. Non avevo invece condiviso il codice di sistema. Qui inizieremo a implementare il codice e in articoli futuri amplieremo le funzionalità del sistema, rendendolo più versatile e completo. Il sistema infatti sembra promettente e ha grandi possibilità di ulteriore miglioramento.


Pianificazione

Per rendere l'idea più comprensibile, ma soprattutto per rendere il sistema espandibile, è stato suddiviso in due file separati mentre il codice principale utilizza i principi OOP (Programmazione orientata agli oggetti). Tutto ciò garantisce che il sistema possa svilupparsi in modo sostenibile, sicuro e stabile.

In questo primo passaggio, utilizzeremo l'indicatore, quindi creiamone uno per questa finestra:

Perché utilizziamo un indicatore e non qualsiasi altro tipo di file? Il motivo è che con un indicatore non è necessario implementare una logica interna aggiuntiva per creare la sottofinestra, invece possiamo dire all'indicatore di farlo, il che ci fa risparmiare tempo e velocizza lo sviluppo del sistema. L'intestazione dell'indicatore apparirà come segue:

#property indicator_plots 0
#property indicator_separate_window


Solo con queste due righe possiamo creare una sottofinestra su un grafico (per chi non sapesse come funzionano, si veda la tabella seguente):

Codice Descrizione
indicator_plots 0 Questa riga informa il compilatore che non terremo traccia di alcun tipo di dati; impedisce al compilatore di mostrare messaggi di avviso.
indicator_separate_window Questa riga indica al compilatore di aggiungere la logica necessaria per creare una sottofinestra.

Questo dovrebbe essere abbastanza facile. Per coloro che non hanno familiarità con la programmazione, alcuni elementi del codice sorgente possono sembrare strane, ma seguono semplicemente protocolli ampiamente utilizzati, accettati dall'intera community di programmazione. Dal momento che MetaTrader 5 utilizza il linguaggio MQL5, molto simile al C++, seppur con lievi differenze, possiamo usare lo stesso metodo di programmare che utilizziamo in C++. In questo modo, le cose diventano molto più facili. Quindi, sfruttando questa somiglianza, possiamo usare una direttiva in linguaggio C come segue:

 #include <Auxiliary\C_TemplateChart.mqh>

Questa direttiva indica al compilatore di includere un file di intestazione che esiste in una determinata posizione. Il percorso completo non dovrebbe essere Includes \ Auxiliary \ C_TemplateChart.mqh!? Sì, il percorso completo è simile a questo, ma MQL5 sa già che qualsiasi file di intestazione dovrebbe trovarsi nella directory 'includes', quindi possiamo omettere la prima parte. Se il percorso è racchiuso tra parentesi angolari, allora è un percorso assoluto; se è racchiuso tra virgolette, il percorso è relativo, cioè <Auxiliary \ C_TemplateChart. mqh> è diverso da "Auxiliary \ C_TemplateChart.mqh".

Continuando con il codice, otteniamo le seguenti righe:

input string user01 = "" ;       //Used indicators
input string user02 = "" ;       //Assets to follow


I valori di stringa possono essere inseriti qui. Se sai chiaramente quale comando vuoi usare quando apri l'indicatore, puoi specificare qui un valore predefinito. Ad esempio, si desidera sempre utilizzare l’RSI con la larghezza della linea di 3 e il MACD con la larghezza della linea di 2. Specificalo per impostazione predefinita come segue:

input string user01 = "RSI:3;MACD:2" ;  //Used indicators
input string user02 = "" ;              //Assets to follow


Il comando può comunque essere modificato in un secondo momento, ma il valore predefinito facilita l'apertura dell'indicatore, poiché sarà già preconfigurato per utilizzare il comando. La riga successiva creerà un alias, attraverso il quale potremo accedere alla classe oggetto che contiene tutto il codice "pesante", permettendoci di accedere alle sue funzioni pubbliche.

C_TemplateChart SubWin;

Il nostro codice all'interno del file dell'indicatore personalizzato è quasi pronto e dobbiamo solo aggiungere altre 3 righe per rendere tutto pronto e funzionante. Naturalmente, la nostra classe di oggetti non contiene errori, ma in questa classe lo vedremo al suo interno. Quindi, per completare il file indicatore, aggiungete le seguenti righe evidenziate in verde:

 //+------------------------------------------------------------------+
int OnInit ()
{
         SubWin.AddThese(C_TemplateChart::INDICATOR, user01);
         SubWin.AddThese(C_TemplateChart::SYMBOL, user02);

         return INIT_SUCCEEDED ;
}
//+------------------------------------------------------------------+

//...... other lines are of no interest to us ......

//+------------------------------------------------------------------+
void OnChartEvent ( const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
{
         if (id == CHARTEVENT_CHART_CHANGE ) SubWin.Resize();
}
//+------------------------------------------------------------------+


Questo è esattamente ciò che ritroveremo nell'indicatore personalizzato. A questo punto, diamo un'occhiata più da vicino alla “scatola nera” del file che contiene la nostra classe di oggetti. D'ora in poi, ci concentreremo su due funzioni. Per semplificare, iniziamo dando uno sguardo alle funzioni presenti nella nostra classe di oggetti e vediamo a cosa serve ciascuna di esse.

Funzioni Descrizione
Imposta Base Crea un oggetto necessario per visualizzare i dati dell'indicatore
decodifica Decodifica il comando passato
Aggiungi formato Regola le cose di conseguenza a seconda del tipo di dati presentati
C_Template Default class constructor
~ C_Template Class destructor
Resize Modifica le dimensioni della sottofinestra
AddThese Funzione responsabile dell'accesso e della costruzione degli oggetti interni

E questo è quanto. Come vedete, utilizziamo le funzioni RESIZE e ADDTHESE nel nostro indicatore personalizzato. Queste sono le uniche funzioni pubbliche al momento, il che significa che abbiamo poco di cui preoccuparci dal momento che tutto il resto è nascosto all'interno del nostro oggetto, il che ci assicura che non venga modificato senza motivo. Ciò fornisce un'elevata affidabilità del nostro codice finale. Procediamo con il codice che inizia con la seguente definizione:

 #define def_MaxTemplates         6

Questa riga è molto importante per la nostra classe di oggetti: definisce il numero massimo di puntatori che possono essere creati. Per aggiungere o rimuovere puntatori, cambiate semplicemente questo numero. Con questa semplice soluzione abbiamo un'allocazione dinamica della memoria e limitiamo il numero di indicatori. Probabilmente, questo è l'unico elemento che potreste voler cambiare. Personalmente, penso che 6 sia un numero adatto per la maggior parte degli utenti e dei monitor utilizzati.

La riga successiva è un'enumerazione che semplifica la gestione dei dati in alcuni punti del programma:

 enum eTypeChart {INDICATOR, SYMBOL};

Il fatto che questa riga sia all'interno della nostra classe garantisce che possa avere lo stesso nome in una classe diversa, ma i dati in essa specificati apparterranno solo a questa classe di oggetti. A questo punto, per accedere correttamente a questo enum, utilizzate il modulo fornito nella funzione OnInit del nostro file indicatore personalizzato. Se il nome della classe viene omesso, si considera come un errore di sintassi e il codice non viene compilato. La riga successiva è una parola riservata.

 private :


Significa che tutto ciò che segue sarà privato per questa classe di oggetti e non sarà visibile al di fuori di essa. Vale a dire, l'accesso a qualsiasi altra cosa sarà impossibile se non siete all'interno della classe. Questo migliora la sicurezza del codice, rendendo i dati privati specifici della classe inaccessibili da qualsiasi altra parte. Ulteriori righe dichiarano alcune variabili interne e private, fino ad arrivare alla prima vera funzione della nostra classe.

 void SetBase( const string szSymbol, int scale)
{
#define macro_SetInteger(A, B) ObjectSetInteger (m_Id, m_szObjName[m_Counter], A, B)

...

         ObjectCreate (m_Id, m_szObjName[m_Counter], OBJ_CHART , m_IdSubWin, 0 , 0 );
         ObjectSetString (m_Id, m_szObjName[m_Counter], OBJPROP_SYMBOL , szSymbol);
        macro_SetInteger( OBJPROP_CHART_SCALE , scale);
...
        macro_SetInteger( OBJPROP_PERIOD , _Period );
        m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );
        m_Counter++;
#undef macro_SetInteger
};


Consideriamo questo segmento di codice SetBase in modo più dettagliato. Iniziamo dichiarando la macro. Dice al compilatore come deve essere interpretato il codice semplificato con il nome della macro. Cioè, se dobbiamo ripetere qualcosa più volte, possiamo usare questa caratteristica del linguaggio C per produrre qualcosa di più semplice. Se per caso dobbiamo cambiare qualcosa, lo cambieremo solo nella macro. Ciò velocizza notevolmente il lavoro e riduce la possibilità di errori nei codici in cui verrà modificato solo uno o un altro argomento.

In questo modo creiamo un oggetto di tipo CHART. Questo può sembrare strano. Perché stiamo usando qualcosa che può essere cambiato per cambiare le cose? Si è vero. Il passaggio successivo consiste nel dichiarare l'asset da utilizzare. Il primo punto qui: se al momento del salvataggio della chat non è presente alcuna risorsa, l'oggetto sarà collegato alla risorsa attualmente utilizzata. Se si tratta del grafico di un asset specifico, in seguito verrà utilizzato esattamente questo asset. Dettaglio importante: potete indicare un asset diverso e utilizzare con esso impostazioni generiche, ma ve lo spiegherò in dettaglio nel prossimo articolo. Inseguito, infatti, implementeremo alcuni miglioramenti in questo codice per poter fare cose che al momento non sono possibili. Successivamente, c'è un livello di densificazione delle informazioni indicato nella proprietà OBJPROP_CHART_SCALE. Useremo valori da 0 a 5. Sebbene possiamo utilizzare valori al di fuori di questo intervallo, è meglio mantenerlo.

La prossima cosa a cui prestare attenzione è la proprietà OBJPROP_PERIOD. Tenete presente che stiamo utilizzando il periodo del grafico corrente e se lo cambiamo, cambierà anche questo. In futuro, apporteremo alcune modifiche che consentiranno di bloccarlo. Se volete provarlo, potete utilizzare un periodo definito da MetaTrader 5, ad esempio PERIOD_M10, che indicherà la visualizzazione dei dati in un periodo fisso di 10 minuti. Ma questo sarà migliorato in seguito. Successivamente, incrementiamo il numero di sub0indicatori di uno e distruggiamo la macro. In questo modo, non avrà più una rappresentazione e dovrà essere ridefinito per essere utilizzato altrove. Non ho forse dimenticato qualcosa? Sì, è la riga che forse è la parte più importante di questo codice.

m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );

Questa riga cattura qualcosa che può essere considerato come un puntatore, anche se in realtà non è un puntatore in se stesso, ma ci permette di fare alcune manipolazioni extra sull'oggetto OBJ_CHART che abbiamo creato. Abbiamo bisogno di questo valore per applicare alcune impostazioni all'interno dell'oggetto. Sono nel file delle impostazioni che abbiamo creato in precedenza. Proseguendo con il codice, arriviamo alla seguente funzione, che può essere vista per intero di seguito:

 void AddTemplate( const eTypeChart type, const string szTemplate, int scale)
{
	if (m_Counter >= def_MaxTemplates) return ;
	if (type == SYMBOL) SymbolSelect (szTemplate, true );
	SetBase((type == INDICATOR ? _Symbol : szTemplate), scale);
	ChartApplyTemplate (m_handle, szTemplate + ".tpl" );
	ChartRedraw (m_handle);
}


Per prima cosa controlliamo se è possibile o meno aggiungere un nuovo indicatore. Se è possibile, verificate se si tratta di un SIMBOLO e, in caso affermativo, allora il SIMBOLO deve essere presente nella finestra Vista del mercato, il che è garantito dalla funzione. Sulla base di ciò, creiamo un oggetto che riceverà informazioni. Al momento dell'esecuzione, il template viene applicato a OBJ_CHART, ed è allora che entra in gioco la magia: chiamiamo di nuovo l'oggetto, ma ora conterrà i dati secondo le definizioni contenute nel file delle impostazioni che è stato utilizzato per definire OBJ_CHART. Ora è semplice, bello e comprensibile.

Si potrebbe fare molto usando queste due funzioni. Ma abbiamo bisogno di almeno un'altra funzione: il suo codice completo è mostrato di seguito:

 void Resize( void )
{
         int x0 = 0 , x1 = ( int )( ChartGetInteger (m_Id, CHART_WIDTH_IN_PIXELS , m_IdSubWin) / (m_Counter > 0 ? m_Counter : 1 ));
         for ( char c0 = 0 ; c0 < m_Counter; c0++, x0 += x1)
        {
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XDISTANCE , x0);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XSIZE , x1);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_YSIZE , ChartGetInteger (m_Id, CHART_HEIGHT_IN_PIXELS , m_IdSubWin));
        }
         ChartRedraw ();
}

Quello che fa la funzione sopra è che mette tutto al suo posto, mantenendo i dati sempre all'interno dell'area della sottofinestra. Per il momento qui c'è altro da aggiungere. A questo punto, abbiamo terminato il codice necessario affinché tutto funzioni perfettamente. Ma che dire delle altre funzioni?! Non preoccupatevi, il resto delle routine non è affatto necessario dal momento che supportano semplicemente l'interpretazione delle righe di comando. Nondimeno, diamo un'occhiata a una cosa importante per chiunque voglia modificare questo codice in futuro. Si tratta della parola riservata che appare nel codice della nostra classe di oggetti:

 public   :

Questa parola garantisce che tutti i dati e le funzioni siano accessibili e visualizzabili da altre parti del codice, anche se non fanno parte della classe oggetto. Quindi qui dichiariamo cosa può essere effettivamente modificato o accessibile da altri oggetti. In effetti, il comportamento di un buon codice orientato agli oggetti è di non consentire mai l'accesso diretto ai dati di un oggetto. In un codice ben progettato, avremo accesso solo ai metodi. Il motivo è semplice: la sicurezza. Quando consentiamo al codice esterno di modificare i dati all'interno di una classe, corriamo il rischio che i dati non corrispondano a ciò che l'oggetto si aspetta e ciò causerebbe molti problemi e mal di testa quando si tenta di risolvere incoerenze o difetti quando tutto sembra a posto. Quindi ecco alcuni consigli che posso dare a qualcuno che ha programmato in C++ per anni: Non consentite MAI agli oggetti esterni di modificare o accedere direttamente ai dati in una classe. Fornite funzioni o routine in modo che sia possibile accedere ai dati, ma non consentite mai l'accesso diretto ai dati. Assicuratevi inoltre che le funzioni e le routine supportino i dati come previsto dalla classe creata. Con questo in mente, passiamo alle ultime due funzioni del nostro tutorial, di cui una pubblica (AddThese) e l'altra privata (Decode). Potete vederli per intero qui sotto:

void Decode( string &szArg, int &iScale)
{
#define def_ScaleDefault 4
         StringToUpper (szArg);
        iScale = def_ScaleDefault;
         for ( int c0 = 0 , c1 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ':' :
                         for (; (c0 < max) && ((szArg[c0] < '0' ) || (szArg[c0] > '9' )); c0++);
                        iScale = ( int )(szArg[c0] - '0' );
                        iScale = ((iScale > 5 ) || (iScale < 0 ) ? def_ScaleDefault : iScale);
                        szArg = StringSubstr (szArg, 0 , c1 + 1 );
                         return ;
                 case ' ' :
                         break ;
                 default :
                        c1 = c0;
                         break ;
        }
#undef def_ScaleDefault
}
//+------------------------------------------------------------------+
// ... Codes not related to this part...
//+------------------------------------------------------------------+
void AddThese( const eTypeChart type, string szArg)
{
         string szLoc;
         int i0;
         StringToUpper (szArg);
         StringAdd (szArg, ";" );
         for ( int c0 = 0 , c1 = 0 , c2 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ';' :
                         if (c1 != c2)
                        {
                                szLoc = StringSubstr (szArg, c1, c2 - c1 + 1 );
                                Decode(szLoc, i0);
                                AddTemplate(type, szLoc, i0);
                        }
                        c1 = c2 = (c0 + 1 );
                         break ;
                 case ' ' :
                        c1 = (c1 >= c2 ? c0 + 1 : c1);
                         break ;
                 default :
                        c2 = c0;
                         break ;
        }
}


Queste due funzioni fanno esattamente quello che ho spiegato sopra: impongono l'integrità dei dati all'interno della classe dell'oggetto, impedendo che dati incoerenti diventino parte dei dati interni della classe. Ricevono una riga di comando e la decodificano seguendo una sintassi predefinita. Tuttavia, non dicono che il comando ricevuto abbia un errore, poiché non è il loro scopo. Il loro scopo è garantire che dati incoerenti non entrino nell'oggetto e non causino effetti collaterali che possono essere difficili da rilevare e correggere.

Il risultato finale sarà il seguente:



Conclusione

Spero che questo codice vi ispiri! Mi sono interessato alla programmazione perché è bella ed eccitante. Anche se spesso è causa di brutti mal di testa, quando proviamo a ottenere dei risultati speciali. La maggior parte delle volte però ne vale la pena. Nel prossimo articolo vi dirò come rendere tutto questo ancora più interessante. Questo allegato all’articolo contiene il codice completo dell'indicatore, già utilizzabile come descritto in questo e nel precedente articolo.


Tradotto dal portoghese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/pt/articles/10230

Scopri come progettare un sistema di trading con Envelopes Scopri come progettare un sistema di trading con Envelopes
In questo articolo, parleremo di uno dei metodi per fare trading utilizzando le bande. Questa volta prenderemo in considerazione le Envelopes. Vi mostrerò quanto è facile creare alcune strategie basate su questo indicatore.
Indicatori multipli su un grafico (parte 01): Comprendere i concetti Indicatori multipli su un grafico (parte 01): Comprendere i concetti
Oggi impareremo come aggiungere più indicatori in esecuzione simultanea su un grafico, ma senza occupare un'area separata. Molti trader si sentono più sicuri se monitorano più indicatori alla volta (ad esempio, RSI, STOCASTIC, MACD, ADX e altri), o in alcuni casi anche i diversi asset di cui è composto un indice.
Indicatori multipli su un grafico (Parte 03): Sviluppo di definizioni per gli utenti Indicatori multipli su un grafico (Parte 03): Sviluppo di definizioni per gli utenti
Oggi aggiorneremo per la prima volta la funzionalità del sistema di indicatori. Nel precedente articolo "Indicatori multipli su un grafico" abbiamo esaminato il codice di base che permette di utilizzare più di un indicatore in una sottofinestra del grafico. Quanto ho presentato è però solo la base di partenza di un sistema molto più ampio.
Scopri come progettare un sistema di trading con le Bollinger Bands Scopri come progettare un sistema di trading con le Bollinger Bands
In questo articolo impareremo a conoscere le bande di Bollinger, uno degli indicatori più popolari nel mondo del trading. Prenderemo in considerazione l'analisi tecnica e vedremo come progettare un sistema di trading algoritmico basato sull'indicatore Bollinger Bands.