English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
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

MetaTrader 5Esempi | 19 luglio 2022, 16:30
63 0
Daniel Jose
Daniel Jose

Introduzione

Nell'articolo precedente, all'interno di 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. A partire da questo modello è possibile poi fare diverse cose. Andremo per gradi, dal momento che uno degli obiettivi principali di questi articoli è quello di stimolarvi a imparare a programmare, per creare i vostri sistemi sulla base delle vostre idee. In questo articolo approfondiremo altri aspetti di questa funzionalità. Potrebbe infatti essere molto interessante per coloro che già conoscono le capacità del sistema, ma che vorrebbero poter fare di più.


Pianificazione

Spesso, quando iniziamo a implementare un nuovo sistema, non abbiamo un quadro chiaro di quanto potremo migliorarlo. Per questo, è sempre opportuno iniziare un nuovo progetto tenendo da subito presente come potrebbe essere migliorato in seguito. Questo è molto importante per chi è agli inizi: progettare costantemente qualcosa di nuovo e immaginare estensioni e miglioramenti futuri.

Il codice principale non è cambiato affatto, il che in un certo senso era già previsto. E’ il codice delle classi di oggetti ad essere cambiato drasticamente. Abbiamo apportato delle modifiche per implementare nuove funzionalità e per apportare miglioramenti che lo rendano più flessibile. In questo senso, il riutilizzo del codice diventa ancora più significativo (e questa è una delle idee di base della programmazione orientata agli oggetti: riutilizzare sempre, creare qualcosa di nuovo solo quando necessario). Diamo quindi un'occhiata alla nuova classe di oggetti. Evidenzierò le modifiche per renderle più comprensibili.

Iniziamo con le nuove definizioni delle variabili private della classe.

struct st
{
        string  szObjName,
                szSymbol;
        int     width;
}m_Info[def_MaxTemplates];
int             m_IdSubWin,
                m_Counter,
                m_CPre,
                m_Aggregate;
long            m_Id,
                m_handle;
ENUM_TIMEFRAMES m_Period;

Si noti che il numero di variabili utilizzate è aumentato in modo significativo. Questo è dovuto alla necessità di più dati per gestire correttamente la nuova funzionalità. Adesso il nostro sistema di variabili ha una struttura definita. Queste strutture sono molto utili per raggruppare variabili correlate e assicurano che quando si interviene sui dati si abbia un accesso facile e veloce.

void SetBase(const string szSymbol, int iScale, int iSize)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
        if (m_IdSubWin < 0)
        {
                m_Id = ChartID();
                m_IdSubWin = (int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - 1;
                m_Aggregate = 0;
        }
        m_Info[m_Counter].szObjName = __FILE__ + (string) MathRand() + (string) ObjectsTotal(m_Id, -1, OBJ_CHART);
        ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, 0, 0);
        ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));

// ....

        macro_SetInteger(OBJPROP_PERIOD, m_Period);
        m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
        m_Aggregate += iSize;
        m_Info[m_Counter].width = iSize;
        m_CPre += (iSize > 0 ? 1 : 0);
        m_Counter++;
#undef macro_SetInteger
};


Il cambiamento principale che vedremo tra poco è l'uso di una struttura per memorizzare il nome della risorsa, il nome dell'oggetto e la sua larghezza. Ora possiamo specificare anche la larghezza che l'indicatore assumerà nella sottofinestra. Prendiamo anche alcuni appunti per utilizzarli in altre parti della classe. Di seguito è riportata la funzione che è cambiata di più.

void Decode(string &szArg, int &iScale, int &iSize)
{
#define def_ScaleDefault 4
#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
                                                                
        string sz1;
        int i0, i1, c1 = StringLen(szArg);
        bool b0 = true;
        StringToUpper(szArg);
        iScale = def_ScaleDefault;
        m_Period = _Period;
        for (int c0 = 0, max = StringLen(szArg); c0 < max; c0++) switch (szArg[c0])
        {
                case ':':
                        b0 = false;
                        for (; (c0 < max) && ((szArg[c0] < '0') || (szArg[c0] > '9')); c0++);
                        iScale = (int)(szArg[c0] - '0');
                        iScale = ((iScale > 5) || (iScale < 0) ? def_ScaleDefault : iScale);
                        break;
                case ' ':
                        break;
                case '<':
                        macro_GetData('>');
                        if (sz1 == "1M") m_Period = PERIOD_M1; else

//....

                        if (sz1 == "1MES") m_Period = PERIOD_MN1;
                        break;
                case '[':
                        macro_GetData(']');
                        iSize = (int) StringToInteger(sz1);
                        break;
                default:
                        c1 = (b0 ? c0 : c1);
                        break;
        }
        szArg = StringSubstr(szArg, 0, c1 + 1);
#undef macro_GetData
#undef def_ScaleDefault
}


Il verde indica le aggiunte al codice. Il giallo è usato per le righe che esistevano già nel codice sorgente ma che sono state spostate per motivi pratici. Vediamo ora come queste aggiunte intervengono sul codice e, soprattutto, cosa migliorano in termini di funzionalità del sistema originale. In realtà, stiamo creando una base per consentire all'utente di personalizzare alcune cose specifiche. Stiamo cercando di farlo aggiungendo nuove regole alla sintassi esistente (vedi la tabella seguente):

Separatore Funzionalità Esempio  Risultato 
< > Specifica il periodo grafico da utilizzare  < 15m > Fissa il periodo dell'indicatore a 15 minuti. Il grafico originale può utilizzare un periodo diverso, ma l'indicatore verrà visualizzato solo con dati a 15 minuti.
 [ ] Specifica la larghezza dell'indicatore  [ 350 ] Fissa la larghezza dell'indicatore a 350 pixel. 

Il separatore che determina il periodo del grafico - solo nella finestra dell'indicatore - non influisce sulle modifiche che l'utente può apportare. Tutti gli altri indicatori e il grafico principale verranno aggiornati al nuovo periodo selezionato dall'utente, ma l'indicatore statico non si adatterà al nuovo periodo. In alcuni casi, questa impostazione può essere interessante, come mostrato nell'immagine seguente.

         

Questa, infatti, facilita notevolmente vari tipi di impostazioni per le quali è necessario avere più grafici dello stesso asset con periodi diversi visibili sullo schermo. Ora un separatore che fissa la larghezza del grafico semplificherà le cose in casi ancora più specifici. Ha anche un altro impiego molto rilevante, che però esploreremo in un altro articolo. Per ora possiamo usarlo per gestire la larghezza dell'indicatore.

È possibile utilizzare una combinazione di tutti i separatori o solo quello realmente necessario per l’indicatore: non esistono regole specifiche. L'unica regola è che il nome dell'indicatore deve essere anteposto a tutto il resto. Torniamo alla spiegazione del codice. Osservate le righe seguenti:

#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
           
//....                                                     
                case '<':
                        macro_GetData('>');


Notate che stiamo definendo qualcosa che a molti potrebbe sembrare strano. Il suo nome, in questo caso, può essere utile: macro_GetData(A) crea infatti un pezzo di codice che è tecnicamente una macro. Quando il compilatore trova questa definizione nel codice, sostituisce la dichiarazione con il codice della macro. Questo è molto utile se si vuole ripetere una certa parte di codice in più punti, ma con modifiche minime tra una dichiarazione e l'altra. La riga in verde dell’esempio precedente verrà ora sostituita e il codice generato dal compilatore sarà il seguente:

case '<':
	b0 = false;
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != '>'); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
//....

Un intervento del genere è l'espressione più chiara della filosofia secondo cui è opportuno riutilizzare il più possibile il codice vecchio e scrivere il meno possibile codice nuovo. Vediamo ora cosa si può cambiare se si vuole rendere più chiara la sintassi. Si tratta di un piccolo dettaglio, ma può essere molto più apprezzabile se adattato al vostro stile personale. Osservate la riga seguente:

//.....

if (sz1 == "1M") m_Period = PERIOD_M1; else

//.....

L’informazione evidenziata è un dettaglio importante. Secondo il modello che ho proposto, se l'utente vuole utilizzare un periodo di 1 minuto, deve indicarlo con la seguente dicitura: MIN_1. Se volete avere uno stile individuale, potete specificarlo a modo vostro. In ogni caso, le lettere devono essere in maiuscolo e senza spazi. Ad esempio, la parte selezionata può essere sostituita con "1MIN", "MIN_1", "1_MINUTO" o con qualcosa di ancora più dettagliato, ad esempio: "LOCK_IN_1_MIN" o nella vostra lingua - Così funzionerà a patto che non ci siano spazi tra le parole. In realtà, questa limitazione dello spazio spazio può essere rimossa, ma mi sembra che non ve ne sia alcuna necessità. Vedete com'è interssante conoscere a fondo la programmazione? Solo così potete imprimere alle cose il vostro stile individuale. Il prossimo codice che ho modificato è il distruttore predefinito.

~C_TemplateChart() 
{
        for (char c0 = 0; c0 < m_Counter; c0++)
        {
                ObjectDelete(m_Id, m_Info[c0].szObjName);
                SymbolSelect(m_Info[c0].szSymbol, false);
        }
}

La riga evidenziata è stata aggiunta per i seguenti casi: se un asset non è aperto in una finestra separata, non dovrebbe più apparire in Vista del mercato. In questo modo si evita che i simboli inutilizzati occupino spazio e sovraccarichino la finestra. Passiamo ora a un argomento che ho promesso di spiegare nell'articolo precedente. Il blocco seguente non esisteva nel codice originale, ma farà parte del codice in futuro.

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

La riga evidenziata consente di utilizzare un file di impostazioni predefinito nel caso in cui tutti i simboli osservati utilizzino le stesse impostazioni. Non serve a nulla fare qualcosa senza capire bene come ci si dovrebbe comportare nel caso in cui le cose non dovessero andare come previsto. Ma se le impostazioni sono ESATTAMENTE uguali, perché non usarle? Si noti che se non viene trovato un file di impostazioni per l'attività specificata, si intende che si vogliono utilizzare le impostazioni predefinite di MetaTrader 5 definite nel file DEFAULT.TPL che si trova nella cartella Profiles\Template. Prima però, dobbiamo capire una cosa importante. Perché non ho indicato una singola directory nella funzione ChartApplyTemplate? Il perché sta nel fatto che MetaTrader 5 esegue la ricerca seguendo una certa logica. Sapere come funziona questa logica può aiutare a comprendere le situazioni in modo più interessante e meno stressante.

Immaginiamo il seguente scenario, in cui sostituiamo la riga evidenziata con quest’altra:

if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""MyTemplates\\Default.tpl");

MetaTrader 5 cercherà innanzitutto il file delle impostazioni nella sottocartella MYTEMPLATES della directory in cui si trova il file eseguibile dell'indicatore personalizzato. In alternativa, se la cartella MYTEMPLATES si trova nella stessa cartella in cui si trova il file eseguibile utilizzato per la creazione di più indicatori, MetaTrader 5 cercherà il file esattamente lì. Diversamente, se non viene trovato nulla lì, MetaTrader 5 cercherà lo stesso file nella cartella MQL5\Profiles\Templates\MyTemplates. Questo è il motivo per cui non l'ho mostrato prima. Ma non finisce qui. C'è un altro dettaglio nello stesso codice:

if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""\\MyTemplates\\Default.tpl");

Un piccolo dettaglio che cambia tutto. Ora MetaTrader 5 cercherà prima di tutto di trovare il file nella cartella MQL5 MyTemplates e nel caso in cui non lo trovasse, seguirebbe gli step descritti sopra. Potete trovare informazioni al riguardo nella documentazione di , perché in questa sede non voglio confondere chi non sa come funziona MetaTrader 5. Ora che sapete come viene eseguita la ricerca, potete creare delle varianti, coscienti di dove mettere il file.

La funzione successiva della nostra classe, che ha subito modifiche significative, è mostrata di seguito:

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
        }
        ChartRedraw();
#undef macro_SetInteger
}

Le righe selezionate mostrano i calcoli più importanti di questa funzione. Le finestre vengono regolate nel modo desiderato dall'utente, ma le finestre che sono state impostate su una dimensione fissa creeranno un'area libera in cui le altre avranno le dimensioni definite. Tuttavia, se la larghezza della finestra principale viene ridotta, il calcolo in blu non consente di ridurre la larghezza delle finestre a dimensione fissa, il che può causare dei problemi. Ma lasciamo le cose come stanno per ora, perché le finestre a larghezza fissa hanno altri vantaggi che saranno meglio esplorati in futuro. L'ultima modifica alla nostra classe è la seguente:

void AddThese(const eTypeChart type, string szArg)
{
        string szLoc;
        int i0, iSize;
//....
        Decode(szLoc, i0, iSize);
        AddTemplate(type, szLoc, i0, iSize);
//....
}

L'unica modifica è in evidenza: non c'è niente di particolare.


Conclusione

Spero che questo articolo, come altri, mostri quanto sia interessante la programmazione strutturata. Con alcune modifiche e la massima tranquillità, possiamo aggiungere molte funzionalità al sistema. Il riutilizzo del codice renderà la manutenzione molto più semplice, perché più usiamo il codice finito, meno è probabile che contenga bug. Nel prossimo articolo, porteremo questo sistema altrove, dove può essere più interessante per molti, e cercheremo di capire ulteriori dettagli sulla programmazione. Ci vediamo...



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

Scopri come creare un sistema di trading con l’indicatore Momentum Scopri come creare un sistema di trading con l’indicatore Momentum
Nel mio precedente articolo ho parlato dell'importanza di identificare la tendenza di mercato, ovvero la direzione dei prezzi. In questo articolo esaminerò uno dei indicatori tecnici più importanti: l'indicatore Momentum. Vedremo insieme come progettare un sistema di trading basato sul Momentum.
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.
Un'analisi del motivo per cui gli Expert Advisor falliscono Un'analisi del motivo per cui gli Expert Advisor falliscono
Questo articolo presenta un'analisi di dati del mercato forex per comprendere meglio perché gli Expert Advisor registrano buone prestazioni in alcuni periodi e deludenti in altri.
Indicatori multipli su un grafico (Parte 02): Primi esperimenti Indicatori multipli su un grafico (Parte 02): Primi esperimenti
Nel precedente articolo "Indicatori multipli su un grafico" ho presentato la logica e le basi relative a come utilizzare più indicatori su un grafico. In questo articolo fornirò il codice sorgente e lo spiegherò in dettaglio.