Indicatori multipli su un grafico (Parte 05): Trasformiamo MetaTrader 5 in un sistema RAD (I)

Daniel Jose | 3 agosto, 2022

Introduzione

Tante persone non sanno programmare ma sono molto creative e hanno grandi idee. Purtroppo però, la mancanza di conoscenze di programmazione impedisce loro di realizzare queste idee. Oggi creeremo la nostra interfaccia Chart Trade per inviare ordini di mercato o per impostare i parametri per ordini pendenti. Lo faremo senza programmare, utilizzando semplicemente le funzioni presenti all'interno dell'Expert Advisor. Siamo curiosi, quindi vediamo che aspetto avrà sui nostri monitor:


Potreste pensare: "Ma come si fa? Non so niente di programmazione, o quello che so non sarà sufficiente per farlo". Chart Trade che vedete nell'immagine sopra è stato creato nella stessa piattaforma MetaTrader 5 ed è stato progettato come mostrato nell'immagine sottostante:


Ora che sappiamo di cosa tratta questo articolo, dovremmo essere entusiasti e pieni di idee per realizzare il nostro grafico personalizzato. Prima però, dobbiamo eseguire alcuni passaggi per far funzionare il tutto. Una volta impostato il codice ausiliario, la nostra creatività sarà l'unico limite per la progettazione del nostro Chart Trade IDE. Questo articolo continua sulla scia dei precedenti quindi, per una comprensione completa ed esaustiva, vi consiglio di partire dall’inizio di questa serie.

Benissimo, mettiamoci al lavoro!


Pianificazione

Per cominciare, è necessario modificare i dati del grafico che si utilizzerà come IDE. E’ necessario farlo per minimizzare possibili effetti collaterali. Il punto è che lasciando il grafico pulito, sarà più facile costruire e progettare l'interfaccia di Chart Trade. Per prima cosa, aprite le proprietà del grafico e impostate le proprietà come mostrato nella figura seguente.

     

In questo modo, lo schermo sarà assolutamente pulito e privo di qualsiasi elemento che possa interferire con lo sviluppo del nostro IDE. Prestate ora attenzione alla seguente spiegazione. Il nostro IDE verrà salvato come file di impostazioni, ovvero come TEMPLATE. In questo modo possiamo utilizzare qualsiasi oggetto fornito da MetaTrader 5 ma, per motivi pratici, ne utilizzeremo solo alcuni. Per vedere tutti gli oggetti disponibili, consultare la sezione Tipi di oggetti in MetaTrader 5.

Oggetto Tipo di coordinate utilizzate per il posizionamento Interessante per l'IDE 
Testo Data e prezzo  NO
Etichetta Posizione X e Y  SÌ
Bottone  Posizione X e Y  SÌ
Grafico  Posizione X e Y  SÌ
Bitmap  Data e prezzo  NO
Etichetta Bitmap  Posizione X e Y  SÌ
Modifica  Posizione X e Y  SÌ
Evento  Viene utilizzata solo la data  NO
Etichetta Rettangolare Posizione X e Y  SÌ

Utilizzeremo un sistema che può essere posizionato in qualsiasi parte dello schermo, motivo per cui non sarebbe pratico utilizzare oggetti che non impiegano il sistema di coordinate X e Y per il posizionamento. Questi ultimi infatti possono far apparire l'IDE in modo completamente diverso. Per questo, ci limiteremo a creare un sistema con sei oggetti, un numero più che sufficiente per creare un'interfaccia.

L'idea è quella di disporre gli oggetti in un ordine logico, simile a quello che utilizzeremmo se dovessimo disegnare qualcosa sullo schermo. Iniziamo creando lo sfondo e poi sovrapponiamo gli oggetti, posizionandoli e regolandoli man mano che si sviluppa l'interfaccia. Ecco come funziona:

    

    

Il tutto è molto semplice. Ci vuole solo un po' di pratica per padroneggiare questo metodo di progettare e creare il proprio IDE. L'idea è molto simile a quella utilizzata nei programmi RAD, usati per creare interfacce di programmazione quando lo sviluppo dell'interfaccia utente tramite codice possa essere molto complesso. Non è che non si possa creare un'interfaccia direttamente attraverso il codice, ma l'uso di questo metodo rende molto più rapide e semplici le ulteriori modifiche, il che è ideale per chi vuole un'interfaccia con il proprio stile.

Dopo aver finito, potremmo ritrovarci con un'interfaccia come quella qui sotto, o con una ancora più bella. Qui ho cercato di utilizzare il maggior numero possibile di oggetti per farveli provare. È possibile creare la propria interfaccia preferita.

Questo è il primo passo per creare il nostro IDE. A questo punto dobbiamo creare un codice che supporti effettivamente questa interfaccia e la renda funzionale. Sarete d’accordo che il semplice fatto di essere in grado di creare la propria interfaccia utente rappresenti una fonte di motivazione, che prenderà forma nel codice.

Il passo successivo consiste nel salvare questa interfaccia come file di impostazioni. Adesso possiamo salvare e utilizzare il codice della versione precedente per visualizzarlo come puntatore. Questo significa che non sarà necessario apportare modifiche significative al codice sorgente. Se adesso volessimo testare la possibilità di ricevere eventi o inviare eventi al nostro IDE, ci renderemmo conto di come non sia possibile. La domanda è: se l'interfaccia è stata creata utilizzando oggetti di MetaTrader 5, perché non è possibile inviare e ricevere eventi da questi oggetti? La risposta è più facile da mostrare che da spiegare. La verifica può essere fatta aggiungendo il seguente codice alla versione originale dell'EA.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        Print(sparam);
                        break;
// .... The rest of the code...
        }
}

Questo codice riporta il nome dell'oggetto che riceve il clic e genera l'evento. In questo caso, l'evento è CHARTEVENT_OBJECT_CLICK. Tuttavia, il messaggio restituito sarà il nome dell'oggetto creato dall'EA, non il nome degli oggetti dell'IDE. Questo potrebbe sembrare un grosso problema, tale da rendere impossibile l'utilizzo del nostro IDE ma, in realtà, c'è una soluzione molto semplice ovvero leggere il file delle impostazioni e poi creare gli oggetti come specificato in questo file. Questo creerà il nostro IDE direttamente sul grafico. Quindi, analizzando il file delle impostazioni (TPL), possiamo trovare i dati da utilizzare.

KEY Descrizione
<chart> Avvia il file delle impostazioni
</chart> Termina il file delle impostazioni
<window> Avvia la struttura degli elementi presenti nel grafico.
</window> Definisce la struttura degli elementi presenti nel grafico
<indicator> Avvia la struttura che fornisce i dati relativi a un indicatore
</indicator> Termina la struttura che fornisce i dati relativi a un indicatore
<object> Avvia la struttura che fornisce i dati di un oggetto.
</object> Termina la struttura che fornisce i dati dell'oggetto.

Questa struttura si presenta come segue all'interno del file TPL.

<chart>

.... DATA

<window>

... DATA

<indicator>

... DATA

</indicator>

<object>

... DATA

</object>

</window>
</chart>

La parte che ci interessa è quella compresa fra <object> e </object>. Possono darsi diverse strutture di questo tipo, ognuna delle quali indica un oggetto unico. Per prima cosa, quindi, dobbiamo modificare la posizione del file, piazzandolo in una posizione da cui possa essere letto. Questa è la cartella FILES. È possibile cambiare la posizione ma, in ogni caso, il file deve trovarsi all'interno dell'albero FILE.

Un dettaglio importante: sebbene il sistema abbia ricevuto una modifica che consente di cancellare il grafico quando si usa il file di configurazione IDE, idealmente si dovrebbe anche avere un file pulito con lo stesso nome nella cartella Profiles\Templates. In questo modo si minimizzano i residui che potrebbero essere presenti nel template predefinito, come abbiamo visto negli articoli precedenti. Le principali modifiche sono evidenziate di seguito:

#include <Auxiliar\Chart IDE\C_Chart_IDE.mqh>
//+------------------------------------------------------------------+
class C_TemplateChart : public C_Chart_IDE
{

 .... Other parts from code ....

//+------------------------------------------------------------------+
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(szTemplate, (type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
        if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
        if (szTemplate == "IDE") C_Chart_IDE::Create(m_IdSubWin);
        ChartRedraw(m_handle);
}
//+------------------------------------------------------------------+
void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(Terminal.Get_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);
                if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0);
        }
        ChartRedraw();
#undef macro_SetInteger
}
//+------------------------------------------------------------------+

... The rest of the code

}

Prestate attenzione al fatto che stiamo aggiungendo l'interfaccia IDE come una nuova classe, ereditata dalla nostra classe originale. Questo significa che la funzionalità della classe originale sarà estesa e non causerà effetti collaterali nel codice originale.

Finora questa è stata la parte più facile. Adesso dobbiamo fare qualcosa di leggermente più complicato che supporti il nostro IDE. Per prima cosa, creiamo un protocollo di messaggi che sarà utilizzato dal sistema. Questo protocollo consentirà al sistema di funzionare come mostrato di seguito:


Notate che adesso è possibile modificare i dati del sistema, cosa irrealizzabile prima di aggiungere un protocollo di messaggistica, che ci permetterà di rendere funzionale il nostro IDE. Definiamo quindi alcune cose:

Messaggio Scopo
MSG_BUY_MARKET Invia un ordine BUY di mercato
MSG_VENDITA_MERCATO Invia un ordine di vendita al mercato
MSG_LEVERAGE_VALUE Dati di leva
MSG_TAKE_VALUE Dati sul take profit
MSG_STOP_VALUE Dati sullo stop loss
MSG_RESULT Dati sul risultato attuale della posizione aperta
MSG_DAY_TRADE Indica se l'operazione sarà chiusa o meno alla fine della giornata.

Questo protocollo rappresenta un passo molto importante. Dopo averlo definito, possiamo infatti apportare modifiche al file delle impostazioni. Quando si apre l'elenco degli oggetti, è necessario modificarlo in modo che abbia questo aspetto:

L'interfaccia che sto mostrando avrà un elenco di oggetti come da immagine. Fate attenzione a quanto segue: Il NOME degli oggetti corrisponde a ciascuno dei messaggi che utilizzeremo. I nomi degli altri oggetti non hanno importanza, perché saranno usati come supporto alla modellazione dell'IDE. Gli oggetti con i nomi dei messaggi, invece, riceveranno e invieranno i messaggi. Se volete utilizzare un numero maggiore di messaggi o un tipo diverso di messaggi, è sufficiente che apportiate le modifiche necessarie al codice della classe e MetaTrader 5 stesso fornirà i mezzi per lo scambio di messaggi tra l'IDE e il codice dell'EA.

Ci resta ancora da studiare il file TPL per imparare come creare la nostra classe oggetto. Scopriamo ora come vengono dichiarati gli oggetti all'interno del file TPL. È vero che avremo meno un accesso limitato alle proprietà degli oggetti nel file TPL rispetto alla programmazione, poiché l'interfaccia del terminale stessa dà meno accesso alle proprietà degli oggetti. L'accesso che avremo sarà comunque sufficiente a far funzionare il nostro IDE.

All'interno del file TPL c'è la struttura di cui abbiamo bisogno: da <object> to </object>. In base ai dati contenuti nella struttura, potrebbe sembrare poco chiaro come scoprire il tipo di oggetto. Se però si osservate con attenzione, potete vedere che il tipo di oggetto è determinato dalla variabile type. Questa assume valori diversi per ciascuno degli oggetti. La tabella seguente mostra gli oggetti che vogliamo utilizzare:

Il valore della variabile TYPE Oggetto di riferimento
102 OBJ_LABEL
103 OBJ_BUTTON
106 OBJ_BITMAP_LABEL
107  OBJ_EDIT
110  OBJ_RECTANGLE_LABEL

La nostra classe sta già iniziando a prendere forma. Ecco il primo codice funzione:

bool Create(int nSub)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                                
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
        m_SubWindow = nSub;
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        return true;
}

Si noti che la prima cosa da fare è aprire il file in modalità di lettura e come file binario. Questo serve a non perdere nulla. Utilizzando l'editor HEXA, il file TPL si presenta come segue. Si noti che inizia con un valore molto interessante.

Sembra confusionario? In realtà, non lo è. Il file utilizza la codifica . Sappiamo che i dati sono organizzati per riga, quindi creiamo una funzione per leggere la riga intera in una sola volta. A tale scopo, scriviamo il seguente codice:

bool FileReadLine(void)
{
        int utf_16 = 0;
        bool b0 = false;
        m_szLine = m_szValue = "";
        for (int c0 = 0; c0 < 500; c0++)
        {
                utf_16 = FileReadInteger(m_fp, SHORT_VALUE);
                if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); return true; } else
                if (utf_16 == 0x003D) b0 = true; else
                if (b0) m_szValue = StringFormat("%s%c", m_szValue, (char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (char)utf_16);
                if (FileIsEnding(m_fp)) break;
        }
        return (utf_16 == 0x003E);
}

La lettura cerca di essere il più efficiente possibile quindi, quando incontriamo un segno di uguale ( = ), lo separiamo già durante la lettura, per non doverlo fare in seguito. Il ciclo limita la stringa a un massimo di 500 caratteri, ma questo valore è arbitrario e può essere modificato se necessario. Per ogni nuova stringa trovata, la funzione restituirà il contenuto della stringa, in modo da poter procedere con l'analisi appropriata.

Avremo bisogno di alcune variabili per supportare il protocollo dei messaggi. Queste sono presentate nel codice sottostante:

class C_Chart_IDE
{
        protected:
                enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};
//+------------------------------------------------------------------+
#define def_HeaderMSG "IDE_"
#define def_MaxObject eEDIT_STOP + 32
//+------------------------------------------------------------------+
        private :
                int             m_fp,
                                m_SubWindow,
                                m_CountObject;
                string          m_szLine,
                                m_szValue;
                bool            m_IsDayTrade;
                struct st0
                        {
                                string  szName;
                                int     iPosX;
                        }m_ArrObject[def_MaxObject];

// ... The rest of the class code....

La definizione def_MaxObject indica il numero massimo di oggetti che possiamo mantenere. Questo numero si ottiene in base al numero di messaggi più un numero extra di oggetti che utilizzeremo. Nel nostro caso abbiamo un massimo di 40 oggetti, ma questo dato può essere modificato se necessario. I primi 8 oggetti saranno utilizzati per lo scambio di messaggi tra l'IDE e MetaTrader 5. L'alias di questi messaggi è visibile nell'enumerazione eObjectsIDE. È importante tenerlo presente nel caso in cui si voglia espandere il sistema o adattarlo ad altro scopo.

Questa è solo la prima parte del sistema di supporto. C'è un altro elemento da prendere in considerazione: la costante relativa al sistema dei messaggi. In effetti, il modo in cui MQL5 tratta le costanti può risultare un po' confusionario per chi programma in C / C++. In C/C++, una costante viene dichiarata nella dichiarazione della variabile stessa. In MQL5 il codice un po' più complicato. Tuttavia, potete conviverci, poiché le costanti vengono utilizzate abbastanza raramente. Di seguito è indicato in grassetto il modo in cui è possibile farlo.

        public  :
                static const string szMsgIDE[];

// ... The rest of the class code....

};
//+------------------------------------------------------------------+
static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                             };
//+------------------------------------------------------------------+

Le costanti definite sono esattamente gli stessi valori utilizzati nei nomi degli oggetti dell'interfaccia. Il sistema è stato progettato per essere insensibile alle maiuscole e alle minuscole. Se si desidera, è possibile modificare questo comportamento, ma non lo consiglio.

Dopo aver completato tutti questi passaggi, passiamo adesso al prossimo. Torniamo al file TPL. Guardate il frammento di file qui sotto:


Dopo aver definito il tipo di oggetto da utilizzare, abbiamo una serie di dati che indicano le proprietà dell'oggetto, come nome, posizione, colore, carattere e così via. Queste proprietà dovrebbero essere trasferite agli oggetti interni. Dal momento che si tratta di un’operazione ripetitiva, possiamo creare una funzione generale che se ne occupi. Il programma sarà il seguente:

bool LoopCreating(ENUM_OBJECT type)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
        int c0;
        bool b0;
        string sz0 = m_szValue;
        while (m_szLine != "</object>") if (!FileReadLine()) return false; else
        {
                if (m_szLine == "name")
                {
                        b0 = false;
                        StringToUpper(m_szValue);
                        for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
                        c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);
                        ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName);
                        ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, 0, 0);
                }
                if (m_szLine == "pos_x"                 ) m_ArrObject[c0].iPosX = (int) StringToInteger(m_szValue);
                if (m_szLine == "pos_y"                 ) macro_SetInteger(OBJPROP_YDISTANCE    , StringToInteger(m_szValue));
                if (m_szLine == "size_x"                ) macro_SetInteger(OBJPROP_XSIZE        , StringToInteger(m_szValue));
                if (m_szLine == "size_y"                ) macro_SetInteger(OBJPROP_YSIZE        , StringToInteger(m_szValue));
                if (m_szLine == "offset_x"              ) macro_SetInteger(OBJPROP_XOFFSET      , StringToInteger(m_szValue));
                if (m_szLine == "offset_y"              ) macro_SetInteger(OBJPROP_YOFFSET      , StringToInteger(m_szValue));
                if (m_szLine == "bgcolor"               ) macro_SetInteger(OBJPROP_BGCOLOR      , StringToInteger(m_szValue));
                if (m_szLine == "color"                 ) macro_SetInteger(OBJPROP_COLOR        , StringToInteger(m_szValue));
                if (m_szLine == "bmpfile_on"            ) ObjectSetString(Terminal.Get_ID()     , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue);
                if (m_szLine == "bmpfile_off"           ) ObjectSetString(Terminal.Get_ID()     , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue);
                if (m_szLine == "fontsz"                ) macro_SetInteger(OBJPROP_FONTSIZE     , StringToInteger(m_szValue));
                if (m_szLine == "fontnm"                ) macro_SetString(OBJPROP_FONT          , m_szValue);
                if (m_szLine == "descr"                 ) macro_SetString(OBJPROP_TEXT          , m_szValue);
                if (m_szLine == "readonly"              ) macro_SetInteger(OBJPROP_READONLY     , StringToInteger(m_szValue) == 1);
                if (m_szLine == "state"                 ) macro_SetInteger(OBJPROP_STATE        , StringToInteger(m_szValue) == 1);
                if (m_szLine == "border_type"           ) macro_SetInteger(OBJPROP_BORDER_TYPE  , StringToInteger(m_szValue));
        }
        m_CountObject += (b0 ? 0 : (m_CountObject < def_MaxObject ? 1 : 0));
        return true;
                        
#undef macro_SetString
#undef macro_SetInteger
}

Ogni oggetto avrà un nome e sarà memorizzato nella posizione appropriata. Eppure la riga evidenziata mostra qualcosa di diverso. Quando creiamo un IDE, questo deve partire dall'angolo in alto a sinistra del grafico, ma questa posizione X non corrisponde necessariamente all'angolo in alto a sinistra della sottofinestra. Questa posizione deve corrispondere infatti all'angolo in alto a sinistra dell'oggetto, OBJ_CHART, a cui l'IDE sarà legato. Questo oggetto viene indicato al momento del caricamento del template IDE, quindi può trovarsi in qualsiasi punto della sottofinestra. Se questo non viene corretto, l'IDE non apparirà nella posizione appropriata. Pertanto, salvate il valore X e utilizzatelo in seguito per visualizzare l'oggetto nella posizione corretta. La funzione che rende l'IDE in modo appropriato è mostrata di seguito.

Le informazioni di base utilizzate negli oggetti sono già definite ma, se fosse necessario aggiungere altre informazioni, sarebbe sufficiente aggiungerle all'insieme dei comandi e modificare la proprietà con il valore appropriato.

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++)
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

Prima di vedere come vengono elaborati i messaggi, analizziamo altre due funzioni altrettanto importanti. Il sistema può ricevere i valori dall'EA ottenuti durante l'inizializzazione. Questi valori devono essere rappresentati e regolati correttamente in modo che, utilizzando Chart Trade, gli ordini possano essere configurati direttamente al suo interno per eseguire un ordine a mercato o impostare un ordine pendente, senza dover richiamare l'EA. Entrambe le funzioni sono illustrate di seguito:

void UpdateInfos(bool bSwap = false)
{
        int nContract, FinanceTake, FinanceStop;

        nContract       = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT));
        FinanceTake = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT));
        FinanceStop = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT));
        m_IsDayTrade = (bSwap ? (m_IsDayTrade ? false : true) : m_IsDayTrade);
        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade);
        NanoEA.Initilize(nContract, FinanceTake, FinanceStop, clrNONE, clrNONE, clrNONE, m_IsDayTrade);
}
//+------------------------------------------------------------------+
void InitilizeChartTrade(int nContracts, int FinanceTake, int FinanceStop, color cp, color ct, color cs, bool b1)
{
        NanoEA.Initilize(nContracts, FinanceTake, FinanceStop, cp, ct, cs, b1);
        if (m_CountObject < eEDIT_STOP) return;
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT, IntegerToString(nContracts));
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT, IntegerToString(FinanceTake));
        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT, IntegerToString(FinanceStop));
        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade = b1);
}

Si noti che l'IDE è collegato al sistema degli ordini, pertanto le modifiche apportate al sistema si rifletteranno sul relativo sistema. In questo modo non dovremo modificare i dati nell'EA come facevamo prima. Ora possiamo farlo direttamente nell'IDE o nel nostro Chart Trade. Ciò è possibile attraverso le due funzioni indicate in precedenza con riferimento al sistema di messaggistica.

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (StringSubstr(szArg, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG) return;
                        szArg = StringSubstr(szArg, 9, StringLen(szArg));
                        StringToUpper(szArg);
                        if ((szArg == szMsgIDE[eBTN_SELL]) || (szArg == szMsgIDE[eBTN_BUY])) NanoEA.OrderMarket(szArg == szMsgIDE[eBTN_BUY]);
                        if (szArg == szMsgIDE[eBTN_CANCEL])
                        {
                                NanoEA.ClosePosition();
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
                        }
                        if (szArg == szMsgIDE[eCHECK_DAYTRADE]) UpdateInfos(true);
                        break;
                case CHARTEVENT_OBJECT_ENDEDIT:
                        UpdateInfos();
                        break;
        }
}

La domanda sorge spontanea: Tutto qui? Sì, è proprio il sistema di messaggistica che permette alla piattaforma MetaTrader 5 di interagire con l'IDE. È molto semplice, devo ammetterlo, ma senza questa funzione l'IDE non funzionerebbe e non sarebbe possibile costruire il sistema. Potrebbe sembrare un po' complicato far funzionare tutto questo in un EA ma in realtà, grazie all'OOP, il codice dell'EA rimarrà super semplice. Sarà invece un po’ più complicato ottenere l'aggiornamento del risultato che apparirà nell'IDE. I valori vengono aggiornati nella funzione OnTick ma, per semplicità, ho utilizzato i dati forniti da MetaTrader 5, quindi la funzione si presenta così. Questa è la parte più importante: questa funzione è infatti la più richiesta di tutte, quindi è necessario che sia anche la più veloce.

void OnTick()
{
        SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT], NanoEA.CheckPosition());
}

In altre parole, a ogni nuova quotazione viene inviato un messaggio alla classe e il valore risultante viene aggiornato nell'operazione. Non dimenticate però che questa funzione deve essere ben ottimizzata, altrimenti si possono avere seri problemi.


Conclusione

A volte sembra impossibile fare alcune cose, ma a me piacciono le sfide. Quanto esposto qui, che vi mostra come realizzare un sistema RAD all'interno di una piattaforma che non è stata originariamente sviluppata per questo, in questo senso è stato piuttosto interessante. Spero che questo sistema, iniziato con qualcosa di semplice, possa motivarvi a cercare ulteriori soluzioni, cosa che poche persone osano fare.

Presto aggiungerò qualcosa di nuovo a questo Expert Advisor, quindi rimanete sintonizzati!