English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
Sviluppare un Expert Advisor per il trading da zero (Parte 24): Fornire robustezza al sistema (I)

Sviluppare un Expert Advisor per il trading da zero (Parte 24): Fornire robustezza al sistema (I)

MetaTrader 5Trading | 14 dicembre 2023, 16:43
431 0
Daniel Jose
Daniel Jose

Introduzione

Alcune cose non sono così semplici, anche se alcune persone potrebbero pensarlo. Il sistema degli ordini è una di quelle cose. Puoi anche creare un sistema più modesto che ti serva perfettamente, come abbiamo fatto nell'articolo Sviluppare un Expert Advisor per il trading da zero, in cui abbiamo creato un sistema di base che può essere utile per molte persone e non abbastanza per altri. Pertanto, è arrivato il momento in cui tutto inizia a cambiare — è così che è nata la prima parte di questa serie sul nuovo sistema di ordini. Questo può essere visto nell'articolo Sviluppare un Expert Advisor per il trading da zero (Parte 18). È qui che abbiamo iniziato a sviluppare un sistema che può essere gestito dall'EA pur essendo supportato da MetaTrader 5. L'idea del sistema era di non avere limiti con gli ordini sul grafico. All'inizio il sistema sembrava piuttosto audace e devo ammettere che il fatto stesso di creare un sistema in cui gli oggetti sarebbero stati mantenuti non dall'EA ma da MetaTrader 5 mi sembrava piuttosto inutile e inefficiente.

Tuttavia il sistema era in fase di sviluppo e nell’articolo Sviluppare un Expert Advisor per il trading da zero (Parte 23) abbiamo sviluppato un sistema fantasma per facilitare la gestione degli ordini, posizioni o livelli di stop (Take Profit e Stop Loss). È stato molto interessante svilupparlo, ma c'era un problema. Se guardi il numero di oggetti utilizzati e visibili rispetto al numero di oggetti supportati da MetaTrader 5, rimarrai sicuramente sorpreso, perché il numero di oggetti supportati sarà sempre maggiore.

In molti casi il problema non è così grave, puoi anche convivere in alcuni momenti. Ma ci sono due problemi che rendono il sistema poco stabile durante i periodi di elevata volatilità del mercato. In alcune situazioni, forzavano l'utente ad agire in modo errato. Questo perché quando il trader aggiunge un ordine pendente, il sistema lo invia al server e il server a volte necessita di più tempo del solito per rispondere. E il sistema indica in alcuni momenti che c'è un ordine e in altri momenti mostra che non c'è. E quando veniva eseguito su posizioni (guarda la documentazione per la differenza tra ordini e posizioni) questo si rivelava ancora più complicato perché non si sapeva se il server eseguiva il comando come previsto.

Esistono diversi modi per risolvere questo problema. Alcuni di essi sono più semplici, altri sono più complessi. Dobbiamo comunque fidarci dell'EA, altrimenti non dovremmo usarlo in nessun caso.


1.0. Pianificazione

Il grosso problema qui è progettare un sistema che possieda due qualità: velocità e affidabilità. In alcuni tipi di sistemi è piuttosto difficile o addirittura impossibile ottenerli entrambi. Quindi, in molti casi, cerchiamo di bilanciare le cose. Ma poiché si tratta di soldi, i NOSTRI soldi, non vogliamo rischiarli acquistando un sistema che non ha queste qualità. Va ricordato che abbiamo a che fare con un sistema che funziona in TEMPO REALE, e questo è lo scenario più difficile in cui può trovarsi uno sviluppatore, poiché dovremmo sempre provare ad avere un sistema estremamente veloce: deve reagire istantaneamente agli eventi, pur mostrando sufficiente affidabilità per non collassare quando cerchiamo di migliorarlo. Pertanto, è chiaro che il compito è piuttosto difficile.

La velocità può essere raggiunta garantendo che le funzioni vengano chiamate ed eseguite nel modo più appropriato, evitando chiamate inutili in momenti ancora più inutili. Ciò fornirà il sistema il più velocemente possibile all'interno del linguaggio. Tuttavia, se vogliamo qualcosa di ancora più veloce, allora dobbiamo scendere al livello del linguaggio macchina, nel qual caso intendiamo Assembly. Ma questo spesso non è necessario, possiamo usare il linguaggio C e ottenere risultati altrettanto buoni.

Uno dei modi per raggiungere la robustezza desiderata è cercare di riutilizzare il codice il più possibile in modo che venga costantemente testato in casi differenti. Ma questo è solo un modo. Un altro modo è utilizzare l'OOP (Programmazione Orientata agli Oggetti). Se questo viene fatto correttamente e adeguatamente in modo che ciascuna classe oggetto non manipoli direttamente i dati della classe oggetto, tranne nel caso dell'ereditarietà, allora sarà sufficiente avere un sistema molto robusto. Ciò a volte riduce la velocità di esecuzione, ma questa riduzione è così piccola che può essere ignorata a causa dell'incremento esponenziale generato dall'incapsulamento fornito dalla classe. Questo incapsulamento fornisce la robustezza di cui abbiamo bisogno.

Come puoi vedere, non è così semplice ottenere velocità e robustezza. Ma la cosa bella è che non dobbiamo sacrificare così tanto, come potresti pensare a prima vista. Possiamo semplicemente controllare la documentazione del sistema e vedere cosa può essere modificato in modo da migliorare le cose. Il semplice fatto che non stiamo cercando di reinventare la ruota è già un buon inizio. Ma ricorda che i programmi e i sistemi sono in costante miglioramento. Quindi, dovremmo sempre cercare di utilizzare il più possibile le cose a disposizione e solo allora, nell’ultimo caso, reinventare davvero la ruota.

Prima che alcuni ritengano superfluo presentare le modifiche apportate in questo articolo o pensino che sto modificando molto il codice senza effettivamente spostarlo, lasciami spiegare: Quando codifichiamo qualcosa non abbiamo davvero idea di come funzionerà il codice finale. Tutto ciò che abbiamo sono gli obiettivi da raggiungere. Una volta raggiunto questo obiettivo, iniziamo a guardare come lo abbiamo raggiunto e a cercare di migliorare le cose per renderle migliori.

Nel caso di un sistema commerciale, sia esso un eseguibile o una libreria, apportiamo le modifiche e lo rilasciamo come aggiornamento. L'utente non ha realmente bisogno di conoscere i percorsi da compiere per raggiungere l'obiettivo, trattandosi di un sistema commerciale. È un bene che in realtà non lo sappia. Ma visto che è un sistema aperto, non voglio farti pensare che si possa sviluppare subito un sistema estremamente efficiente, fin dall'inizio. Pensare in questo modo non è adeguato, è addirittura un insulto, poiché non importa quanto un programmatore o uno sviluppatore abbia conoscenza del linguaggio da utilizzare, ci saranno sempre cose che possono essere migliorate nel tempo.

Quindi non prendete questa sequenza come qualcosa che si potrebbe riassumere in 3 o 4 articoli, perchè se così fosse sarebbe meglio creare semplicemente il codice, rimanendo nella modalità che ritenevo più opportuna e rilasciarlo commercialmente . Questa non è la mia intenzione. Ho imparato a programmare guardando il codice di altri programmatori più esperti e conosco il valore che questo ha. È molto più importante sapere come si sviluppa la cosa nel tempo che semplicemente prendere la soluzione finita e cercare di capire come funziona.

Dopo queste osservazioni passiamo allo sviluppo.


2.0. Implementazione

2.0.1. Nuova modellazione degli indicatori di posizione

La prima cosa da notare nel nuovo formato del codice è la modifica di una funzione che è diventata una macro.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}


Anche se il compilatore utilizza questo codice in ogni punto in cui viene referenziato grazie alla parola riservata 'inline', non bisogna darlo per scontato, perché questa funzione viene chiamata molte volte nel codice. Dobbiamo assicurarci che funzioni effettivamente il più velocemente possibile, quindi il nostro nuovo codice sarà simile a questo:

#define macroMountName(ticket, it, ev, Ghost) 								 \
		StringFormat("%s%c%llu%c%c%c%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,          \                                                                                                                                                                                                                                                                                                
                                                       ticket, def_SeparatorInfo,                        \                                                                                                                                                                                                                                        
                                                       (char)it, def_SeparatorInfo,                      \ 
                                                       (char)(Ghost ? ev + 32 : ev), def_SeparatorInfo,  \ 
                                                       (Ghost ? def_IndicatorGhost : def_IndicatorReal))

Prestate attenzione che i dati nella vecchia versione della macro e i dati in questa versione sono diversi. C’è una ragione per questa modifica, di cui parleremo più avanti in questo articolo.

Ma a causa di questa modifica dobbiamo apportare anche una piccolo cambiamento al codice di un'altra funzione.

inline bool GetIndicatorInfos(const string sparam, ulong &ticket, eIndicatorTrade &it, eEventType &ev)
                        {
                                string szRet[];
                                char szInfo[];
                                
                                if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
                                if (szRet[0] != def_NameObjectsTrade) return false;
                                ticket = (ulong) StringToInteger(szRet[1]);
                                StringToCharArray(szRet[2], szInfo);
                                it = (eIndicatorTrade)szInfo[0];
                                StringToCharArray(szRet[3], szInfo);
                                ev = (eEventType)szInfo[0];

                                return true;
                        }


La modifica qui è stata solo nell'indice, che verrà utilizzato per indicare cos'è un ticket e cos'è un indicatore. Niente di complicato. C'è solo un semplice dettaglio da fare, altrimenti avremo dati incoerenti quando utilizziamo questa funzione.

Potresti chiederti: "Perché abbiamo bisogno di questi cambiamenti? Il sistema non funzionava perfettamente?". Sì, l'ha fatto. Ma ci sono cose che non possiamo controllare. Ad esempio, quando lo sviluppatore MetaTrader 5 migliora alcune funzioni che non vengono utilizzate nell'EA e quindi non possono esserci utili. La regola è evitare di reinventare la ruota e utilizzare invece le risorse disponibili. Pertanto, dovremmo sempre cercare di utilizzare le funzioni fornite dai linguaggi, che nel nostro caso è MQL5, ed evitare di creare funzioni nostre. Questo può sembrare assurdo, ma in realtà se ti fermi a pensare, vedrai che di tanto in tanto la piattaforma apporta miglioramenti in alcune funzioni, e se utilizzi queste stesse funzioni avrai prestazioni migliori e maggiore sicurezza nei tuoi programmi senza dover fare alcuno sforzo in più.

Quindi il fine giustifica i mezzi. Tuttavia, le modifiche apportate sopra aiuteranno l'EA a trarre vantaggio da eventuali miglioramenti nella libreria MQL5? La risposta a questa domanda è NO. Le modifiche di cui sopra sono necessarie per garantire che la modellazione dei nomi degli oggetti sia corretta in modo da poter utilizzare in modo efficace i possibili miglioramenti futuri provenienti dagli sviluppatori MQL5 e MetaTrader 5. Di seguito è riportato uno degli elementi che potrebbero essere utili:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_InfoSelection.bIsMovingSelect = false;
        ChartRedraw();
}


La versione precedente dello stesso codice è mostrata di seguito per coloro che non lo ricordano o non l'hanno mai incontrato prima. Il codice è simile al seguente:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        {
                macroDestroy(IT_RESULT, true);
                macroDestroy(IT_RESULT, false);
                macroDestroy(IT_PENDING, true);
                macroDestroy(IT_PENDING, false);
                macroDestroy(IT_TAKE, true);
                macroDestroy(IT_TAKE, false);
                macroDestroy(IT_STOP, true);
                macroDestroy(IT_STOP, false);
        } else
        {
                macroDestroy(it, true);
                macroDestroy(it, false);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
}


Potrebbe sembrare che il codice sia appena diventato più compatto. Ma non è solo questo. La riduzione del codice è una cosa ovvia, ma la verità è molto più profonda. Il vecchio codice è stato sostituito da uno nuovo che utilizza meglio le risorse della piattaforma. Ma poiché il modello dei nomi degli oggetti utilizzato in precedenza non consentiva questo miglioramento, abbiamo modificato la modellazione in modo da poter beneficiare delle funzioni MQL5. Se questa funzione dovesse mai essere migliorata per qualsiasi motivo, l'EA trarrà vantaggio da questa modifica senza la necessità per noi di apportare modifiche alla struttura dell'EA. Sto parlando della funzione ObjectsDeleteAll . Se la utilizziamo correttamente, MetaTrader 5 eseguirà la pulizia. Non abbiamo bisogno di specificare troppi dettagli, specifichiamo semplicemente il nome dell'oggetto o degli oggetti e lasciamo che MetaTrader 5 faccia il resto. Nel nuovo codice sono evidenziati i punti in cui viene utilizzata questa funzione. Nota come abbiamo modellato per informare sul prefisso che verrà utilizzato. Ciò non era possibile prima della modifica della modellazione dei nomi degli oggetti.

Vorrei attirare la vostra attenzione su un dettaglio nel nuovo frammento di codice, che è evidenziato di seguito.

if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));


Perché pensi che io abbia aggiunto la parte evidenziata?

Questo perché se il sistema crea un ticket a partire da un valore uguale a 1, allora non appena verrà inserito l'ordine pendente, tutti gli oggetti verranno rimossi dallo schermo. Non è chiaro? L'input utilizzato per inserire un ordine pendente ha un valore di 1, cioè l'indicatore 0 in realtà ha un valore di 1, non 0, poiché 0 viene utilizzato per eseguire altri test nell'EA. Per questo motivo il valore iniziale è 1. Ora abbiamo un problema: supponiamo che il sistema di trading crei un ticket 1221766803. Quindi l'oggetto che rappresenta questo ticket avrà come prefisso il seguente valore: SMD_OT#1221766803. Quando l'EA esegue la funzione ObjectsDeleteAll per eliminare l'indicatore 0, il nome dell'oggetto sarà SMD_OT#1 e questo eliminerà tutti gli oggetti che iniziano con questo valore, incluso il sistema appena creato. Per risolvere questo problema, apporteremo una leggera modifica al nome per informare la funzione ObjectsDeleteAll aggiungendo un carattere in più alla fine del nome in modo che la funzione sappia se stiamo eliminando l'indicatore 0 o un altro.

Pertanto, se l'indicatore 0 deve essere cancellato, la funzione riceve il valore SMD_OT#1#. Ciò eviterà il problema. Allo stesso tempo, nel caso dell'esempio precedente, la funzione assumerà il nome SMD_OT#1221766803*. Sembra essere qualcosa di semplice, ma per questo motivo potresti rimanere perplesso sul motivo per cui l'EA continua a eliminare gli oggetti indicatori di un ordine appena piazzato.

Parliamo ora di un dettaglio curioso. Alla fine della funzione c'è una chiamata di ChartRedraw. A cosa serve qui? MetaTrader 5 non aggiorna il grafico da se? Lo fa. Ma non sappiamo esattamente quando accadrà. C'è un altro problema: tutte le chiamate per piazzare o cancellare oggetti sul grafico sono sincrone, cioè vengono eseguite in un certo momento, che non è necessariamente il momento che ci aspettiamo. Tuttavia, il nostro sistema di ordini utilizzerà gli oggetti per visualizzare o gestire gli ordini e dobbiamo essere sicuri che l'oggetto sia sul grafico. Non possiamo permetterci di pensare che MetaTrader 5 abbia già piazzato o rimosso oggetti dal grafico, perché dobbiamo esserne sicuri, ecco perché forziamo la piattaforma a effettuare questo aggiornamento.

Pertanto, quando chiamiamo ChartRedraw, forziamo la piattaforma ad aggiornare l'elenco degli oggetti sul grafico, in modo da poter essere sicuri che un determinato oggetto sia presente o meno sul grafico. Se questo ancora non è chiaro passiamo al prossimo argomento.


2.0.2. Meno oggetti - maggiore velocità

La funzione di inizializzazione nella versione precedente era macchinosa. Aveva molti controlli ripetitivi e alcune cose erano doppie. Oltre ad alcuni problemi minori, il sistema ha riutilizzato solo una minima parte della capacità già esistente. Pertanto, per sfruttare la nuova modellazione, ho deciso di ridurre il numero di oggetti creati durante l'inizializzazione. Quindi, ora il sistema appare così:

void Initilize(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = OrdersTotal(); c0 >= 0; c0--) IndicatorInfosAdd(OrderGetTicket(c0));
        for (int c0 = PositionsTotal(); c0 >= 0; c0--) IndicatorInfosAdd(PositionGetTicket(c0));
}


Sembra che tutto fosse diverso, e in effetti lo era. Ora stiamo riutilizzando la funzione che non è stata utilizzata abbastanza - questa è la funzione che aggiunge indicatori al grafico. Diamo un'occhiata a questa caratteristica speciale.

inline void IndicatorAdd(ulong ticket)
{
        char ret;
                                
        if (ticket == def_IndicatorTicket0) ret = -1; else
        {
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if ((ret = GetInfosTradeServer(ticket)) == 0) return;
        }
        switch (ret)
        {
                case  1:
                        CreateIndicatorTrade(ticket, IT_RESULT);
                        PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                        break;
                case -1:
                        CreateIndicatorTrade(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                        break;
        }
        ChartRedraw();
        UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
}

Osserva attentamente il codice sopra. Potrebbe sembrare che il codice contenga controlli non necessari. Ma ci sono per una ragione molto semplice. Questa funzione è l'unico modo per creare effettivamente un ordine pendente o un indicatore di posizione. Le due linee evidenziate controlleranno se l'indicatore esiste. A tale scopo viene verificato se nell'oggetto utilizzato come riga è memorizzato qualche valore. Qui è il valore del prezzo a cui si trova l'oggetto. Questo valore deve essere diverso da zero se l'oggetto indicatore è sul grafico. In tutti gli altri casi sarà uguale a zero, o perché l'oggetto non esiste, o per qualsiasi altro motivo, non ha importanza. Ora è chiaro il motivo per cui dobbiamo forzare l'aggiornamento del grafico? Se ciò non venisse fatto, l'EA aggiungerebbe oggetti inutilmente, quindi non possiamo aspettare che la piattaforma intraprenda questa azione in un momento sconosciuto. Dobbiamo essere sicuri che il grafico sia stato aggiornato. Altrimenti, una volta eseguiti questi controlli, riporteranno cose che effettivamente non corrispondono allo stato corrente degli oggetti, rendendo il sistema meno affidabile.

Anche se sembra che questi controlli rallentino la velocità dell'EA, si tratta di un errore concettuale. Quando eseguiamo questi controlli e non proviamo a forzare la piattaforma a creare un oggetto che potrebbe già essere nella coda di creazione, diciamo alla piattaforma "AGGIORNA ORA". Poi, quando ne abbiamo bisogno, controlliamo se l'oggetto è già stato creato e, nel caso sia già stato creato, lo utilizziamo secondo necessità. Questo si chiama "programmare nel modo giusto". Poiché in questo modo facciamo funzionare meno la piattaforma ed evitiamo controlli inutili sulla creazione o meno dell'oggetto, rendiamo l'EA più affidabile, perché sappiamo che abbiamo i dati con cui vogliamo lavorare.

Poiché i controlli mostreranno che non esiste alcun oggetto corrispondente al ticket specificato, l'oggetto verrà creato. Fai attenzione che all'inizio c'è un altro controllo per verificare se stiamo creando l'indicatore 0 o qualsiasi altro. Ciò garantisce di non avere oggetti non necessari supportati da MetaTrader 5; abbiamo solo gli oggetti che effettivamente utilizziamo sul grafico. Se creiamo l'indicatore 0, non saranno necessari ulteriori test, poiché lo creeremo in condizioni molto speciali e specifiche. L'oggetto 0 viene utilizzato per posizionare gli ordini utilizzando SHIFT o CTRL + mouse. Non preoccuparti, vedremo presto come funziona.

C'è un dettaglio importante nel codice sopra: perché stiamo aggiornando il grafico prima di chiamare la funzione di Aggiornamento? È inutile. Per capirlo, diamo un'occhiata alla funzione UpdateIndicators di seguito.

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0 = false;
                                
        pr = macroGetLinePrice(ticket, IT_RESULT);
        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
        SetTextValue(ticket, IT_PENDING, vol);
        if (tp > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_TAKE, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_TAKE);
                PositionAxlePrice(ticket, IT_TAKE, tp);
                SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        }
        if (sl > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_STOP, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_STOP);
                PositionAxlePrice(ticket, IT_STOP, sl);
                SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
        }
        if (b0) ChartRedraw();
}

Questa funzione si occuperà sostanzialmente degli indicatori che puntano ai limiti. Ora dai un'occhiata alle due linee evidenziate: se il grafico non viene aggiornato, queste linee non si attiveranno, restituendo il valore di 0, e se lo fa, il resto del codice non funzionerà e gli indicatori di limite non saranno visualizzati correttamente sullo schermo.

Ma prima di creare gli indicatori limite bisogna fare alcune verifiche per capire se è davvero necessario crearli o semplicemente aggiustarli. Questo viene fatto allo stesso modo di quando si crea l'oggetto di base. E anche qui, in fase di creazione degli oggetti, forzeremo anche l'aggiornamento del grafico in modo che il grafico sia sempre aggiornato.

Potresti chiederti: "Perché ci sono così tanti aggiornamenti forzati, sono davvero necessari?" E la risposta a questa domanda è GRANDE e SUONA ... e il motivo è la funzione seguente:

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};


Potresti pensare che non ci sia nulla di speciale in questa funzione. Sei sicuro? SBAGLIATO! Questa funzione contiene un punto fondamentale: dobbiamo assicurarci che l'oggetto sia sul grafico, altrimenti tutto il codice per crearlo verrà chiamato diverse volte, creando una lunga coda che sarà gestita da MetaTrader 5, e alcuni dati potrebbero andare persi o diventare obsoleti. Tutto ciò renderà il sistema instabile, meno sicuro e quindi inaffidabile. Viene evidenziata la chiamata della funzione che crea l'oggetto. Se non forzassimo MetaTrader 5 ad aggiornare il grafico nei momenti strategici, allora potremmo avere dei problemi, poiché la funzione di cui sopra viene chiamata dall'evento OnTick e durante i periodi di elevata volatilità, il numero di chiamate provenienti da OnTick è piuttosto elevato, il che può dar luogo ad un eccesso di oggetti in coda, il che non va affatto bene. Pertanto, i dati vengono forzatamente aggiornati tramite la chiamata ChartRedraw e convalidati tramite ObjectGetDouble, riducendo così la possibilità che ci siano troppi oggetti in coda.

Anche senza guardare come funziona il sistema, potresti pensare: "È positivo che ora, in caso di eliminazione accidentale dell'oggetto TradeLine, l'EA lo noterà e se il controllo tramite ObjectGetDouble fallisce e l'indicatore fallisce, l'indicatore verrà ricreato." Questa è l'idea. Ma non è consigliabile per l'utente eliminare gli oggetti presenti nella finestra dell'elenco degli oggetti senza sapere veramente quale sia l'oggetto, perché se elimini un oggetto (tranne TradeLine), l'EA potrebbe non notare che non è presente alcun indicatore, lasciandolo senza mezzi di accesso, poiché semplicemente non ha altro modo di accedere se non tramite i pulsanti presenti su di esso.

Lo script qui sopra sarebbe un vero incubo se non fosse per la funzione che lo segue e che è responsabile del mantenimento dell'intero flusso di messaggi all'interno della classe. Tuttavia, non è ancora l’unico punto di ingresso. Sto parlando della funzione DispatchMessage, diamogli un'occhiata.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,
                bKeySell,
                bEClick;
        datetime        dt;
        uint            mKeys;
        char            cRet;
        eIndicatorTrade it;
        eEventType      ev;
                                
        static bool bMounting = false, bIsDT = false;
        static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.pr = price;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
                                        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
                                        m_InfoSelection.bIsMovingSelect = bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        RemoveIndicator(def_IndicatorTicket0);
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, it, ev))
                        {
                                if (GetInfosTradeServer(ticket) == 0) break;
                                CreateIndicatorTrade(ticket, it);
                                if ((it == IT_PENDING) || (it == IT_RESULT))
                                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                                ChartRedraw();
				m_TradeLine.SpotLight();
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                        {
                                                case IT_PENDING:
                                                case IT_RESULT:
                                                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                                        break;
                                                case IT_TAKE:
                                                case IT_STOP:
							m_InfoSelection.ticket = ticket;
							m_InfoSelection.it = it;
                                                        m_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
                                        }
                                        break;
                        }
                        break;
        }
}


Questa funzione ha subito così tanti cambiamenti che dovrò scomporla in piccole parti per spiegare cosa succede al suo interno. Se hai già esperienza di programmazione, allora non ti sarà difficile capire cosa fa. Tuttavia, se sei solo un appassionato o un programmatore MQL5 alle prime armi, allora comprendere questa funzione può essere un po' difficile, quindi te la spiegherò con calma nel prossimo argomento.


2.0.3. Suddivisione della funzione DispatchMessage

Questo argomento spiega cosa succede nella funzione DispatchMessage. Se capisci come funziona semplicemente guardando il codice, questo argomento non ti darà nulla di nuovo.

La prima cosa che abbiamo dopo le variabili locali sono le variabili statiche.

static bool bMounting = false, bIsDT = false;
static double valueTp = 0, valueSl = 0, memLocal = 0;


Potrebbero essere dichiarate come variabili private nella classe, ma poiché verranno utilizzate solo a questo punto del codice, non ha senso che altre funzioni nella classe vedano queste variabili. Dovrebbero essere dichiarate statiche, perché devono ricordare i loro valori quando la funzione viene chiamata nuovamente. Se non aggiungiamo la parola chiave 'static', perderanno il loro valore non appena la funzione terminerà. Fatto ciò, inizieremo a elaborare gli eventi che MetaTrader 5 indica all'EA.

Il primo evento può essere visto qui sotto:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed


Qui raccogliamo e isoliamo i dati dal mouse e da alcuni tasti (della tastiera) associati al mouse. Fatto ciò, arriva un lungo codice che inizia con un test.

if (bKeyBuy != bKeySell)


Se premi il tasto SHIFT o CTRL, ma non entrambi contemporaneamente, questo farà capire all'EA che desideri effettuare un ordine ad un determinato prezzo. Se è così, controlla ulteriormente.

if (!bMounting)
{
        Mouse.Hide();
        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        m_InfoSelection.it = IT_PENDING;
        m_InfoSelection.pr = price;
}


Nel caso in cui l'indicatore 0 non sia stato ancora impostato, questo test verrà superato. Il mouse verrà nascosto, quindi verranno catturati i valori nel Chart Trade. Questi valori vengono poi convertiti in punti in base al livello di leva finanziaria indicato dal trader tramite Chart Trade. Verrà mostrato il valore iniziale in cui verrà effettuato l'ordine. Questa sequenza dovrebbe verificarsi solo una volta per ciclo di utilizzo.

Il passo successivo è creare i livelli Take Profit e Stop Loss e indicare se compreremo o venderemo.

m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
m_InfoSelection.bIsBuy = bKeyBuy;


Vengono creati al di fuori del ciclo perché quando spostiamo il mouse su una fascia di prezzo diversa, dovremo spostare anche il Take Profit e lo Stop Loss. Ma perché questo codice sopra non è all'interno del test assembly? Il motivo è che se si cambia, si rilascia il tasto SHIFT e si preme il tasto CTRL, o viceversa, senza muovere il mouse, mentre sono presenti indicatori sullo schermo, verranno scambiati i valori degli indicatori Take Profit e Stop Loss. Per evitare ciò, il frammento deve rimanere fuori dal test. Ma questo ci obbliga a fare un nuovo test assembly, che vediamo di seguito:

if (!bMounting)
{
        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
        m_InfoSelection.bIsMovingSelect = bMounting = true;
}

Perché abbiamo due test? Possiamo averne solo uno? Questo sarebbe l'ideale, ma la funzione evidenziata nel codice sopra non ci consente di farlo. Dobbiamo guardare IndicatorAdd per comprendere questo fatto. Dopo aver creato l'indicatore 0, lo impostiamo come selezionato e mostriamo che è già in esecuzione e creato. Pertanto, puoi spostarlo con la riga successiva.

MoveSelection(price);


Tuttavia, anche con lo stesso criterio di premere SHIFT o CTRL per effettuare un ordine pendente, abbiamo un passaggio finale.

if ((bEClick) && (memLocal == 0))
{
        RemoveIndicator(def_IndicatorTicket0);
        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
}


Ciò aggiungerà un ordine pendente esattamente al punto a cui stiamo puntando. Devono essere soddisfatte due condizioni. Il primo è il clic del pulsante sinistro del mouse e il secondo è che non l'abbiamo fatto allo stesso prezzo in una volta sola. Cioè, per piazzare due o più ordini allo stesso prezzo, dobbiamo piazzare questo nuovo ordine con una chiamata diversa, perché ciò non avverrà nella stessa chiamata. 

Contemporaneamente alla rimozione dell'indicatore 0 dal grafico, un ordine con i parametri correttamente compilati viene inviato al server di trading.

Ora passiamo allo step successivo...

if (bKeyBuy != bKeySell)
{

// ... code described so far ....

}else if (bMounting)
{
        RemoveIndicator(def_IndicatorTicket0);
        Mouse.Show();
        memLocal = 0;
        bMounting = false;
}

Se era impostato l'indicatore 0 ma la condizione non era soddisfatta perché è stato premuto solo SHIFT o CTRL, allora il codice evidenziato viene eseguito per rimuovere l'indicatore 0 dall'elenco degli oggetti, reimpostando contemporaneamente il mouse e lasciando le variabili statiche nel loro stato iniziale. In altre parole, il sistema sarà pulito.

Il passaggio successivo e finale nella gestione degli eventi del mouse è mostrato di seguito:

if (bKeyBuy != bKeySell)
{

// ... previously described code ...

}else if (bMounting)
{

// ... previously described code ...

}else if ((!bMounting) && (bKeyBuy == bKeySell))
{
        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
}



Il codice evidenziato è l'ultimo passaggio del mouse nell'elaborazione del messaggio. Nel caso in cui non abbiamo impostato l'indicatore 0 né i tasti SHIFT o CTRL in uno stato diverso, il che significa che possono essere premuti o rilasciati contemporaneamente, avremo il seguente comportamento: se facciamo clic con il tasto sinistro, il prezzo verrà inviato all'indicatore, e se spostiamo solo il mouse, il prezzo verrà utilizzato per spostare l'indicatore. Ma allora sorge una domanda: quale indicatore? Non preoccuparti, vedremo presto di quale indicatore si tratta, ma nel caso te lo stessi chiedendo, l'indicatore 0 non utilizza questa selezione. Se non capisci, torna all'inizio di questa sezione e leggi come funziona questa elaborazione dei messaggi.

Di seguito è riportato il messaggio successivo:

case CHARTEVENT_OBJECT_DELETE:
        if (GetIndicatorInfos(sparam, ticket, it, ev))
        {
                if (GetInfosTradeServer(ticket) == 0) break;
                CreateIndicatorTrade(ticket, it);
                if ((it == IT_PENDING) || (it == IT_RESULT))
                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                ChartRedraw();
		m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
        }
        break;


Ricordate, ho detto sopra che l'EA ha un piccolo sistema di sicurezza per impedire la rimozione errata degli indicatori? Questo sistema è contenuto nel codice per l'elaborazione dei messaggi sugli eventi inviati da MetaTrader 5 quando un oggetto viene eliminato.

Quando ciò accade, MetaTrader 5 riporta, utilizzando il parametro sparam, il nome dell'oggetto eliminato rispetto al quale viene controllato se si trattava di un indicatore e, in caso affermativo, quale. Non importa quale oggetto è stato colpito. Ciò che vogliamo sapere è quale indicatore è stato interessato, dopodiché controlleremo se esiste un ordine o una posizione associata all'indicatore e, in tal caso, creeremo nuovamente l'intero indicatore. In un caso estremo, se l'indicatore interessato fosse l'indicatore base, lo riposizioniamo immediatamente e forziamo MetaTrader 5 a posizionare immediatamente l'indicatore sul grafico, indipendentemente da quale sia l'indicatore. Rimuoviamo l'indicazione di selezione e inviamo un ordine per l'aggiornamento sui dati di soglia dell'indicatore. 

Il prossimo evento da gestire è molto semplice, richiede semplicemente di ridimensionare tutti gli indicatori sullo schermo, il suo codice è mostrato di seguito.

case CHARTEVENT_CHART_CHANGE:
        ReDrawAllsIndicator();
        break;

Ecco l'evento clic sull'oggetto.

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
//....
        }
        break;


Inizia come mostrato sopra: MetaTrader 5 ci dice quale oggetto è stato cliccato in modo che l'EA possa verificare quale tipo di evento gestire. Finora abbiamo 2 eventi CLOSE e MOVE. Consideriamo innanzitutto l'evento CLOSE, che chiuderà e definirà la fine dell'indicatore sullo schermo.

case EV_CLOSE:
        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
        {
                case IT_PENDING:
                case IT_RESULT:
                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                        break;
                case IT_TAKE:
                case IT_STOP:
			m_InfoSelection.ticket = ticket;
			m_InfoSelection.it = it;
                        m_InfoSelection.bIsMovingSelect = true;
                        SetPriceSelection(0);
                        break;
        }
        break;


L'evento close farà quanto segue: utilizzerà il ticket per cercare nel server cosa dovrebbe essere chiuso e per verificare se c'è qualcosa da chiudere, perché può succedere che a questo punto il server lo abbia già fatto ma l'EA non lo sa ancora. Dato che abbiamo qualcosa da chiudere, facciamolo correttamente in modo da avere i controlli richiesti e il modo corretto per informare la classe di chiudere o rimuovere un indicatore dal grafico.

Quindi, siamo arrivati ​​​​all'ultimo step di questo argomento, mostrato di seguito.

case EV_MOVE:
        if (m_InfoSelection.bIsMovingSelect)
        {
                m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
        }else
        {
                m_InfoSelection.ticket = ticket;
                m_InfoSelection.it = it;
                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
        }
        break;


MOVE è un evento che fa esattamente questo — seleziona l'indicatore da spostare. Quindi seleziona solo, ma il movimento stesso viene eseguito durante un evento di movimento del mouse. Ricorda, all'inizio dell'argomento, ho detto che esiste una condizione in cui non abbiamo a che fare con l'indicatore 0 e anche così, qualcosa si muoverà comunque. Questo qualcosa viene indicato a questo punto, nell'evento move. Controlliamo qui se è selezionato qualcosa da spostare. In tal caso, l'indicatore selezionato cesserà di essere selezionato e non riceverà gli eventi di movimento del mouse e il nuovo indicatore non verrà selezionato. In questo caso i dati del nuovo indicatore per ricevere i dati del mouse verranno memorizzati in una struttura e questo indicatore riceverà una modifica che indicherà che è selezionato. Questo cambiamento è visibile nello spessore della linea.


2.0.4. Una nuova classe Mouse Object

Oltre ai miglioramenti di cui abbiamo parlato sopra, ne abbiamo altri che meritano di essere menzionati.

Mentre la maggior parte dei trader non ha bisogno di un sistema di indicatori basato sul mouse implementato in un EA, altri ne potrebbero aver bisogno e volere che il sistema funzioni perfettamente. Ma il trader potrebbe eliminare per errore alcuni degli oggetti che compongono l'indicatore del mouse, il che porterà al suo fallimento. Fortunatamente, possiamo evitarlo utilizzando il sistema EVENT. Una volta rilevato e inviato un evento di eliminazione di un oggetto all'EA, la classe a cui appartiene l'oggetto può ricreare nuovamente l'oggetto, conferendo stabilità al sistema. Ma è bene mantenere la lista dei punti il ​​più piccola possibile, crearli quando necessario e cancellarli quando non servono più. Questo è quello che abbiamo fatto finora, ma mancava la classe Mouse.

Iniziamo creando alcune definizioni per sostituire il sistema di creazione delle costanti dei nomi.

#define def_MousePrefixName "MOUSE "
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TMPV"
#define def_NameObjectLineT def_MousePrefixName + "TMPT"
#define def_NameObjectBitMp def_MousePrefixName + "TMPB"
#define def_NameObjectText  def_MousePrefixName + "TMPI"


Successivamente, la nuova funzione di inizializzazione si presenta così:

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        Show();
}


Tieni presente che è molto più semplice rispetto alla versione precedente. A questo punto abbiamo la chiamata che mostrerà il sistema del mouse. La chiamata viene effettuata nel punto evidenziato nel codice precedente. Chiamerà il codice che creerà di fatto un sistema di indicazione sull'asse dei prezzi.

inline void Show(void)
{
        if (ObjectGetDouble(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_PRICE) == 0)
        {
                ObjectCreate(Terminal.Get_ID(), def_NameObjectLineH, OBJ_HLINE, 0, 0, 0);
                ObjectSetString(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_TOOLTIP, "\n");
                ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_BACK, false);
        }
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, m_Infos.cor01);
}

Questo codice è molto interessante: controlla se l'oggetto puntatore del mouse esiste nel prezzo oppure no. Se il controllo ha esito positivo, allora significa che sul grafico è presente una linea o qualcosa correlato al mouse, quindi tutto ciò che dobbiamo fare è regolare il colore della linea orizzontale. Perché eseguiamo questo controllo? Per capirlo, dai un'occhiata alla funzione preposta a nascondere, o meglio rimuovere, gli oggetti collegati al mouse. Vedi la funzione qui sotto:

inline void Hide(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(Terminal.Get_ID(), def_MousePrefixName + "T");
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
}


Questo è uno stile operativo interessante. Tutti gli oggetti collegati al mouse e aventi il ​​nome specificato verranno eliminati dal grafico di MetaTrader 5 e quindi la lista degli oggetti sarà sempre piccola. Tuttavia, la linea orizzontale non verrà eliminata, cambierà solo il suo colore. Quindi la funzione che mostra il mouse effettua un controllo prima di creare l'oggetto, perché in realtà questo non viene escluso dalla lista degli oggetti ma solo nascosto. Ma tutti gli altri oggetti vengono eliminati dall'elenco degli oggetti. Ma allora come utilizzeremo questi altri oggetti durante gli studi? Poiché gli studi sono brevi momenti in cui vogliamo semplicemente scoprire alcuni dettagli, non ha senso conservare gli oggetti nell'elenco solo per vederli 1-2 volte. È meglio crearli, fare lo studio e poi rimuoverli dall'elenco, in modo da ottenere un sistema più affidabile.

Questo può sembrare sciocco, ma il sistema di ordini che mostriamo si basa sull'uso di oggetti, e più oggetti sono nell'elenco, più lavoro dovrà fare MetaTrader 5 nel cercare nella lista, quando vogliamo accedere ad un determinato oggetto. Quindi non lasceremo oggetti extra sul grafico o nell'elenco degli oggetti, manteniamo il sistema il più leggero possibile.

Ora presta attenzione alla funzione DispatchMessage che inizia così:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;


Subito dopo abbiamo il codice che inizierà a gestire il primo evento.

switch (id)
{
        case CHARTEVENT_MOUSE_MOVE:
                Position.X = (int)lparam;
                Position.Y = (int)dparam;
                ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                ObjectMove(Terminal.Get_ID(), def_NameObjectLineH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                if (b1 > 0) ObjectMove(Terminal.Get_ID(), def_NameObjectLineV, 0, Position.dt, 0);
                key = (uint) sparam;
                if ((key & 0x10) == 0x10)    //Middle button....
                {
                        CreateObjectsIntern();
                        b1 = 1;
                }


Quando premiamo il pulsante centrale del mouse, generiamo una chiamata. Ma non è il caso ora. Poi vedremo cosa fa questa funzione. Tieni presente che stiamo tentando di spostare un oggetto che non esiste perché non è nell'elenco degli oggetti supportati da MetaTrader 5. Questa chiamata avverrà solo quando viene premuto il pulsante centrale del mouse. Da notare la variabile b1 che controlla a che punto il trader si trova all'interno dell'insieme coinvolto nella generazione dello studio.

Non appena l'utente fa clic con il pulsante sinistro del mouse e il primo passaggio è completato, avremo in esecuzione il seguente codice:

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

Posizionerà la trend line e chiamerà il passaggio successivo in cui viene modificato il valore della variabile b1. A questo punto possiamo passare al frammento successivo.

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), def_NameObjectBitMp, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), def_NameObjectText, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), def_NameObjectText, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}


Questo frammento sopra è ciò che mostrerà effettivamente lo studio sullo schermo. Tutti questi oggetti presenti in questo frammento non esisteranno al termine dello studio, verranno creati e distrutti durante questa routine. Sebbene non sembri molto efficiente fare questo, non ho notato alcuna diminuzione o aumento del tempo di elaborazione durante la fase di studio. In effetti, ho notato un leggero miglioramento nel sistema degli ordini, qualcosa di molto sottile, che rientra praticamente nel margine di errore della stima comparativa. Quindi non posso dire che questi cambiamenti abbiano effettivamente portato miglioramenti in termini di elaborazione.

Ma tieni presente che lo studio verrà eseguito mentre si tiene premuto il pulsante sinistro del mouse; non appena lo rilasceremo, verrà eseguito il frammento successivo.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}
Position.ButtonsStatus = (b1 == 0 ? key : 0);


Qui rimuoviamo tutti gli oggetti utilizzati per creare lo studio dall'elenco degli oggetti. Mostriamo di nuovo la linea del mouse sullo schermo. Il codice evidenziato è un'ottima idea in quanto impedisce a qualsiasi funzione o subroutine all'interno dell'EA di ottenere letture false quando catturiamo i pulsanti del mouse. Se viene condotto uno studio, l'EA dovrebbe ignorare gli stati dei pulsanti. A questo scopo, utilizziamo le linee evidenziate. Non è una soluzione ottimale ma meglio di niente.

Non abbiamo considerato il codice che crea gli oggetti per eseguire lo studio. Ma poiché si tratta di una funzione abbastanza semplice, non mi concentrerò su di essa nell'articolo.


Conclusioni

Sebbene le modifiche possano sembrare piccole, fanno tutte una grande differenza per il sistema stesso. C'è una cosa da ricordare: il nostro sistema di comando si basa su oggetti grafici sullo schermo, quindi più oggetti elabora l’EA, minore sarà la sua prestazione quando richiediamo un particolare oggetto. A complicare ulteriormente la situazione, il sistema funziona in tempo reale, cioè più veloce è il sistema del nostro EA, migliore sarà la sua prestazione. Pertanto, meno cose deve fare l'EA, meglio è. Idealmente, dovrebbe essere in grado di funzionare solo con il sistema degli ordini, e dovremmo portare tutto il resto ad un altro livello, e MetaTrader 5 dovrebbe occuparsene. Lo faremo, ovviamente, gradualmente, poiché dovremo apportare molte piccole modifiche, ma niente di troppo complicato. Ciò verrà fatto nei prossimi articoli dedicati esclusivamente al miglioramento dell'affidabilità dell'EA.

Posso dire con certezza una cosa: in futuro, l'EA sarà responsabile solo del sistema degli ordini. Nel prossimo articolo daremo all'EA un aspetto finale molto interessante: ridurremo ulteriormente il numero di oggetti presenti nella lista durante il funzionamento dell'EA, poiché il sistema di ordini è un grande generatore di oggetti, e vedremo come modificare questo sistema in modo tale da ridurre al minimo il carico che crea su MetaTrader 5.

Per questo motivo non allego alcuna modifica a questo articolo in quanto il codice stesso sarà comunque soggetto a modifiche. Ma non preoccuparti, vale la pena aspettare il prossimo articolo. Queste modifiche aumenteranno in modo significativo le prestazioni complessive del nostro Expert Advisor. Quindi, ci vediamo al prossimo articolo di questa serie.


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

Sviluppare un Expert Advisor di trading da zero (Parte 25): Fornire robustezza al sistema (II) Sviluppare un Expert Advisor di trading da zero (Parte 25): Fornire robustezza al sistema (II)
In questo articolo, faremo il passo finale verso le prestazioni dell'EA. Preparatevi quindi a una lunga lettura. Per rendere il nostro Expert Advisor affidabile, per prima cosa rimuoveremo dal codice tutto ciò che non fa parte del sistema di trading.
Imparare come progettare un sistema di trading con l’Alligator Imparare come progettare un sistema di trading con l’Alligator
In questo articolo completeremo la nostra serie su come progettare un sistema di trading basato sugli indicatori tecnici più popolari. Impareremo a creare un sistema di trading basato sull'indicatore Alligator.
Un esempio di come assemblare i modelli ONNX in MQL5 Un esempio di come assemblare i modelli ONNX in MQL5
ONNX (Open Neural Network eXchange) è un formato aperto costruito per rappresentare le reti neurali. In questo articolo mostreremo come utilizzare contemporaneamente due modelli ONNX in un Expert Advisor.
Impara come progettare un sistema di trading tramite Accelerator Oscillator Impara come progettare un sistema di trading tramite Accelerator Oscillator
Un nuovo articolo della nostra serie su come creare semplici sistemi di trading tramite gli indicatori tecnici più popolari. Ne impareremo uno nuovo, che è l'indicatore Accelerator Oscillator, e impareremo a progettare un sistema di trading che lo utilizzi.