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

MetaTrader 5Esempi | 8 gennaio 2024, 12:59
232 0
Daniel Jose
Daniel Jose

Introduzione

Nel precedente articolo Fornire robustezza al sistema (I), abbiamo visto come modificare alcune parti dell'EA per rendere il sistema più affidabile e robusto.

Questa era solo un'introduzione a ciò che faremo in questo articolo. Dimenticate tutto ciò che sapevate, che avevate pianificato o desiderato. La cosa più difficile è riuscire a separare le cose. Dall'inizio di questa serie, l'EA si è evoluto quasi costantemente: abbiamo aggiunto, cambiato e persino rimosso alcune cose. Questa volta andremo agli estremi di ciò che abbiamo fatto.

Contrariamente a quanto può sembrare, c'è un grosso problema: un EA ben progettato non contiene e non conterrà alcun tipo di indicatore al suo interno. Si limita a osservare e a garantire il rispetto delle posizioni d'ordine indicate. L'EA perfetto è essenzialmente una procedura guidata che fornisce una visione reale dell'andamento del prezzo. Non esamina gli indicatori, ma solo le posizioni o gli ordini presenti sul grafico.

Potreste pensare che sto dicendo sciocchezze e che non so di cosa sto parlando. Ma avete mai pensato perché MetaTrader 5 fornisce classi differenti per cose diverse? Perché la piattaforma presenta indicatori, servizi, script ed Expert Advisor separatamente e non in un unico blocco? Quindi...

Questo è il punto. Se le cose sono separate, è proprio perché è meglio lavorarle separatamente.

Gli indicatori vengono utilizzati per uno scopo generale, qualunque esso sia. È bene che la progettazione degli indicatori sia ben studiata in modo da non danneggiare le prestazioni complessive — non intendo danneggiare la piattaforma MetaTrader 5, ma altri indicatori. Poiché vengono eseguiti su un thread differente, possono eseguire compiti in parallelo in modo molto efficiente.

I servizi forniscono assistenza in diversi modi. Ad esempio, negli articoli Accedere ai dati sul web (II) e Accedere ai dati sul web (III) di questa serie, abbiamo utilizzato i servizi per accedere ai dati in modo molto interessante. In realtà, potremmo farlo direttamente nell'EA, ma questo non è il metodo più adatto, come ho già spiegato in altri articoli.

Gli script ci aiutano in un modo molto particolare, perché esistono solo per un certo periodo di tempo, fanno qualcosa di molto specifico e poi scompaiono dal grafico. Oppure possono rimanere lì fino a quando non si cambia qualche impostazione del grafico, come ad esempio il timeframe.

Questo limita un po' le possibilità, ma fa parte di cosa dobbiamo accettare così com'è. Gli Expert Advisor o EA, al contrario, sono specifici per lavorare con un sistema di trading. Sebbene sia possibile aggiungere funzioni e codici che non fanno parte del sistema di trading negli EA, questo non è molto appropriato nei sistemi ad alte prestazioni o ad alta affidabilità. Il motivo è che tutto ciò che non fa parte del sistema di trading non dovrebbe essere presente nell'EA: le cose dovrebbero essere collocate nei posti giusti e gestite correttamente.

Pertanto, la prima cosa da fare per migliorare l'affidabilità è rimuovere dal codice tutto ciò che non fa parte del sistema di trading e trasformarle in indicatori o qualcosa del genere. L'unica cosa che rimarrà nel codice dell'EA sono le parti responsabili della gestione, dell'analisi e dell'elaborazione degli ordini o delle posizioni. Tutte le altre cose saranno rimosse.

Quindi, iniziamo.


2.0. Implementazione

2.0.1. Rimozione dello sfondo dell’EA

Anche se questo non danneggia l'EA né causa alcun problema, a volte alcuni desiderano che lo schermo sia vuoto con solo alcuni elementi visualizzati. Pertanto, rimuoveremo questa parte dall'EA e la trasformeremo in un indicatore. È molto facile da implementare. Non toccheremo nessuna classe, ma creeremo il seguente codice:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Background image type
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

Come potete vedere, tutto è abbastanza naturale e comprensibile. Abbiamo semplicemente eliminato il codice dall'EA e lo abbiamo convertito in un indicatore che può essere aggiunto al grafico. Qualsiasi modifica, sia essa lo sfondo, il livello di trasparenza o addirittura la sua rimozione dal grafico, non avrà alcun effetto sulle azioni dell'EA.

Ora inizieremo a eliminare gli elementi che realmente causano il degrado delle prestazioni dell’EA. Queste sono le cose che funzionano di tanto in tanto o ad ogni movimento di prezzo, e quindi a volte possono causare un rallentamento dell'EA, che gli impedisce di svolgere il suo vero lavoro - osservare ciò che sta accadendo con gli ordini o le posizioni sul grafico.


2.0.2. Conversione di Volume at Price in un indicatore

Anche se non sembra, il sistema Volume At Price richiede tempo, che spesso è fondamentale per un’EA. Mi riferisco ai momenti di alta volatilità in cui i prezzi fluttuano selvaggiamente senza una direzione precisa. È in questi momenti che l'EA ha bisogno di ogni ciclo macchina disponibile per completare il suo compito. Sarebbe sconvolgente perdere una buona opportunità perché un indicatore decide di prendere il lavoro. Quindi, rimuoviamolo dall'EA e trasformiamolo in un vero indicatore creando il codice sottostante:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Bar color
input   char            user1   = 20;                                   //Transparency (from 0 to 100 )
input color     user2 = clrForestGreen; //Buying
input color     user3 = clrFireBrick;   //Selling
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Questa è stata la parte più facile. Abbiamo rimosso il codice dall'EA e lo abbiamo inserito nell'indicatore. Se si desidera reinserire il codice nell'EA, è sufficiente copiare il codice dell'indicatore e reinserirlo nell'EA.

Abbiamo quindi iniziato con qualcosa di semplice. Ma ora le cose diventano più complicate — stiamo per rimuovere Times & Trade dall'EA.


2.0.3. Trasformare Times & Trade in un indicatore

Non è così semplice se vogliamo creare un codice che possa essere utilizzato sia in un EA che in un indicatore. Essendo un indicatore che funziona in una sottofinestra, sembrerebbe facile convertirlo in un indicatore. Ma non è facile, proprio perché funziona in una sottofinestra. Il problema principale è che se facessimo tutto come nei casi precedenti, si otterrà il seguente risultato nella finestra dell'indicatore:

Non è consigliabile inserire queste cose nella finestra dell'indicatore, in quanto ciò confonderebbe l'utente nel caso in cui volesse rimuovere l'indicatore dallo schermo. Quindi, questo dovrebbe essere fatto in un modo differente. Alla fine di questo percorso, che può sembrare al quanto confuso ma che in realtà è un semplice insieme di direttive e qualche modifica, otterremo il seguente risultato nella finestra dell'indicatore.

Questo è esattamente ciò che l'utente si aspetta — non il pasticcio che si vede nell'immagine precedente.

Di seguito è riportato il codice completo dell'indicatore Times & Trade:

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Scale
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Il codice sembra simile a quello utilizzato nell'EA, tranne per la riga evidenziata che non è presente nel codice dell'EA. Allora qual è la fregatura? O non c'è? In realtà, c'è qualche problema: il codice non è esattamente lo stesso, c'è una differenza che non è nel codice dell'indicatore o dell'EA, ma nel codice della classe. Ma prima di considerare la differenza, pensiamo a quanto segue: come facciamo a dire al compilatore cosa compilare e cosa no? Forse, quando si programma, non ci si preoccupa affatto di questo — forse si crea semplicemente del codice e se qualcosa non piace, lo si cancella.

I programmatori esperti hanno una regola: rimuovere qualcosa solo quando non funziona assolutamente, altrimenti mantenere i frammenti anche se non vengono effettivamente compilati. Ma come fare in un codice lineare, quando vogliamo che le funzioni scritte funzionino sempre? Ecco la domanda: Sapete come indicare al compilatore cosa compilare e cosa no? Se la risposta è "No", allora va bene. Personalmente non sapevo come farlo quando ho iniziato. Ma è di grande aiuto. Scopriamo quindi come si fa.

Alcuni linguaggi dispongono di direttive di compilazione, che possono essere chiamate anche preprocessore, a seconda dell'autore. Ma l'idea è la stessa: dire al compilatore cosa compilare e come farlo. Esiste un tipo di direttiva molto specifica che può essere usata per isolare intenzionalmente il codice in modo da poter testare cose specifiche. Queste sono le direttive di compilazione condizionale. Se usate correttamente, ci permettono di compilare lo stesso codice in modi differenti. Questo è esattamente ciò che viene fatto qui con l'esempio di Times & Trade. Si sceglie chi sarà responsabile della generazione della compilazione condizionale: se l'EA o l'indicatore. Dopo aver definito questo parametro, create la direttiva #define e poi usate la direttiva condizionale #ifdef #else #endif per informare il compilatore su come verrà compilato il codice.

Può essere difficile da capire, quindi vediamo come funziona.

Nel codice dell'EA, definire e aggiungere le linee evidenziate di seguito:

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

Si verifica quanto segue: Se si vuole compilare un EA con classi in file MQH, lasciare la direttiva #define def_INTEGRATION_WIT_EA che è definita nell'Expert Advisor. In questo modo, l'EA conterrà tutte le classi che prendiamo e inseriamo negli indicatori. Se si desidera eliminare gli indicatori, non è necessario eliminare il codice, ma è sufficiente commentare la definizione. Questo può essere fatto semplicemente convertendo la riga in cui è dichiarata la direttiva in una riga di commento. Il compilatore non vedrà la direttiva, che verrà data come inesistente; dato che non esiste, ogni volta che la direttiva condizionale #ifdef def_INTEGRATION_WITH_EA viene trovata, sarà completamente ignorata, mentre il codice compreso tra essa e la direttiva #endif nell'esempio precedente non verrà compilato.

Questa è l'idea che implementiamo nella classe C_TimesAndTrade. Ecco come appare la nuova classe. Mostrerò solo un punto per attirare la vostra attenzione:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

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

}

Il codice può sembrare strano a chi non usa le direttive di compilazione. Il def_INTEGRATION_WITH_EA è dichiarato nell'EA. Poi si verifica quanto segue. Quando il compilatore genera il codice oggetto da questo file, assumerà la seguente relazione: se il file in fase di compilazione è un EA e ha una direttiva dichiarata, il compilatore genererà codice oggetto con parti che si trovano tra le direttive condizionali #ifdef def_INTEGRATION_WITH_EA e #else. Di solito in questi casi si usa la direttiva #else. Nel caso in cui venga compilato un altro file, ad esempio l'indicatore la cui direttiva def_INTEGRATION_WITH_EA non è definita, tutto ciò che è compreso tra le direttive #else e #endif verrà compilato. Ecco come funziona.

Durante la compilazione di un EA o di un indicatore, è necessario esaminare l'intero codice della classe C_TimesAndTrade per comprendere ciascuno di questi test e il funzionamento generale. In questo modo, il compilatore MQL5 effettuerà tutte le impostazioni, risparmiando tempo e fatica associati alla necessità di mantenere due file differenti.


2.0.4. Rendere l'EA più agile

Come già accennato, l'EA dovrebbe funzionare solo con il sistema di ordini. Finora ha posseduto le caratteristiche che ora sono diventate indicatori. Il motivo è molto personale e ha a che fare con le cose coinvolte nei calcoli che l'EA deve fare. Ma questo sistema di calcolo è stato modificato e spostato in un altro metodo. A causa di ciò, ho notato che il sistema degli ordini era danneggiato da alcune cose che l'EA faceva invece di occuparsi degli ordini. Il problema peggiore era nell'evento OnTick:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

Questo evento ha ora ricevuto questa direttiva condizionale in modo che coloro che non fanno trading durante i periodi di alta volatilità possano, se lo desiderano, avere un EA con tutti gli indicatori originali. Ma prima che pensiate che sia una buona idea, lasciate che vi ricordi come funziona la funzione di aggiornamento di Times & Trade.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

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

        }
}

Il codice sopra riportato fa parte della funzione di aggiornamento presente nella classe C_TimesAndTrade. Il problema è nella parte evidenziata. Ogni volta che viene eseguito, viene inviata al server una richiesta di restituzione di tutti i tick del trade effettuati da un certo momento in poi, il che, tra l'altro, non è così problematico. Il problema è che di tanto in tanto questa chiamata coincide con altri due eventi.

Il primo e il più ovvio evento è il gran numero di operazioni che possono avvenire, che fa sì che la funzione OnTick riceva un gran numero di chiamate. Oltre a dover eseguire il codice sopra, presente nella classe C_TimesAndTrade, questa funzione si occuperà di un altro problema: chiamare la funzione SecureChannelPosition presente nella classe C_IndicatorTradeView. Si tratta quindi di un altro piccolo problema, ma non è tutto. Ho già detto che di tanto in tanto, nonostante la bassa volatilità, si verifica la coincidenza di due eventi, il primo dei quali è stato questo.

Il secondo è nell'evento OnTime, che è già stato aggiornato e si presenta come segue:

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

Se si intende utilizzare l'EA nel modo in cui è stato progettato, siccome riceve ancora più codice, a volte può avere problemi a causa della coincidenza degli eventi. Quando ciò accade, l'EA rimarrà (anche se per un solo secondo) a fare cose che non sono legate al sistema di ordini.

A differenza della funzione presente in C_TimesAndTrade, questa funzione è presente nella classe C_VolumeAtPrice e può davvero danneggiare le prestazioni dell'EA quando gestisce gli ordini. Ecco perché questo accade:

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

Il motivo è nelle parti evidenziate, ma la peggiore di esse è REDRAW. Questo danneggia enormemente le prestazioni dell'EA perché ad ogni tick ricevuto con volume MAGGIORE al valore specificato, l'intero volume al prezzo viene rimosso dallo schermo, ricalcolato e rimesso al suo posto. Ciò avviene ogni secondo circa. Questo potrebbe coincidere con altre cose, motivo del perché tutti gli indicatori sono stati rimossi dall'EA. Anche se li ho lasciati in modo che possiate utilizzarli direttamente nell'EA, vi sconsiglio comunque di farlo per i motivi spiegati in precedenza.

Queste modifiche erano necessarie. Ma ce n'è un altro, più emblematico, che deve essere fatto. Questa volta la modifica riguarda l'evento OnTradeTransaction. L'uso di questo evento è un tentativo di rendere il sistema il più flessibile possibile. Molti di coloro che programmano EA per l'esecuzione di ordini utilizzano l'evento OnTrade per verificare quali ordini sono o non sono presenti sul server o quali posizioni sono ancora aperte. Non sto dicendo che stiano sbagliando. È solo che non è molto efficiente in quanto il server ci informa di ciò che sta accadendo. Ma il problema principale dell'evento OnTrade è che dobbiamo continuare a controllare le cose inutilmente. Se utilizziamo l'evento OnTradeTransaction, avremo un sistema almeno più efficiente in termini di analisi dei movimenti. Ma non è questo l'obiettivo. Ognuno utilizza il metodo che meglio si adatta ai propri criteri.

Quando ho sviluppato questo EA, ho deciso di non utilizzare alcuna struttura di archiviazione e quindi di non limitare il numero di ordini o posizioni che possono essere lavorati. Ma questo fatto complica talmente la situazione da rendere necessaria un'alternativa all'evento OnTrade, che si può trovare nell'evento OnTradeTransaction.

Questo evento è molto difficile da implementare e probabilmente è il motivo per cui non viene utilizzato da molti. Ma non avevo scelta. O funziona o non funziona, altrimenti le cose sarebbero complicate. Ma nella versione precedente il codice per questo evento era molto inefficiente e lo si può vedere qui sotto:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

Sebbene il codice di cui sopra funzioni, è a dir poco ORRIBILE. Il numero di chiamate inutili generate dal codice sopra è pazzesco. Nulla può migliorare l'EA in termini di stabilità e affidabilità se il codice sopra non può essere corretto.

Per questo motivo, ho eseguito alcune operazioni su un conto demo per cercare di trovare uno schema nei messaggi, cosa che in realtà è piuttosto difficile. Non ho trovato uno schema, ma ho trovato qualcosa che evitava la follia delle chiamate inutili che venivano generate, rendendo il codice stabile, affidabile e allo stesso tempo abbastanza flessibile da poter operare in qualsiasi momento sul mercato. Naturalmente, ci sono ancora alcuni piccoli bug da risolvere, ma il codice è molto buono:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

Non cercate di capire subito cosa sta succedendo, ma godetevi la bellezza di questa funzione. È quasi la perfezione vivente. Non lo dico perché l'ho fatta io, ma per il grado di robustezza e agilità che possiede.

Anche se può sembrare complicato, questo codice prevede due controlli. Sono evidenziati di seguito per spiegare meglio cosa sta succedendo.

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... inner code ...

}

La linea evidenziata in VERDE controllerà ogni volta che si verifica un'operazione nella cronologia per verificare se l'asset in questione è lo stesso osservato dall'EA. In tal caso, la classe C_IndicatorTradeView riceverà un comando per eliminare l'indicatore dal grafico. Questo può accadere in due casi: quando un ordine diventa una posizione e quando una posizione si chiude. Si noti che utilizzo solo la modalità NETTING e non HEDGING. Pertanto, qualunque cosa accada, l'indicatore verrà rimosso dal grafico.

Ci si può chiedere: Se la posizione viene chiusa, va bene; ma se l'ordine diventa una posizione — sarò impotente? No. Ma il problema è risolto non all'interno dell'errore, bensì all'interno della classe C_IndicatorTradeView. Ne parleremo nella prossima sezione dell'articolo.

La linea rossa, invece, riduce assurdamente la quantità di messaggi inutili che sono stati inoltrati alla classe C_IndicatorTradeView. Questo viene fatto controllando la risposta restituita dal server alla richiesta, quindi dobbiamo ottenere una conferma rilanciando la richiesta con lo stesso nome dell'asset che l'EA sta tracciando. Solo allora verrà inviata una nuova serie di chiamate alla classe C_IndicatorTradeView.

Questo è tutto ciò che posso dire su questo sistema. Ma la storia non è ancora finita. Abbiamo molto lavoro da fare e d'ora in poi ci concentreremo solo sulla classe C_IndicatorTradeView. Inizieremo ora con alcune modifiche da apportare.


2.0.5. Riduzione del numero di oggetti creati da C_IndicatorTradeView

Nell'articolo Sviluppare un Expert Advisor di trading da zero (Parte 23) ho introdotto un concetto piuttosto astratto, ma molto interessante, di spostamento degli ordini o dei livelli di stop. Il concetto era quello di utilizzare posizioni fantasma o ombre. Definiscono e visualizzano sul grafico ciò che il server di trading vede e vengono utilizzati fino a quando non si verifica la mossa effettiva. Questo modello ha un piccolo problema: aggiunge oggetti che devono essere gestiti da MetaTrader 5, ma gli oggetti aggiunti non sono necessari nella maggior parte dei casi, quindi MetaTrader 5 ottiene una lista di oggetti che spesso è piena di cose inutili o raramente utilizzate.

Ma non vogliamo che l'EA crei continuamente oggetti o che mantenga nell'elenco oggetti non necessari, perché questo degrada le prestazioni dell'EA. Poiché utilizziamo MetaTrader 5 per gestire gli ordini, dovremmo eliminare gli oggetti inutili che interferiscono con l'intero sistema.

Ma c'è una soluzione molto semplice. In realtà non è così semplice. Apporteremo altre modifiche alla classe C_IndicatorTradeView per migliorarla. Terremo i fantasmi sullo schermo e utilizzeremo un metodo molto curioso e poco utilizzato.

Sarà divertente e interessante.

Per prima cosa, modificheremo la struttura della selezione. L'aspetto è ora il seguente:

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
			bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_Selection;

Non vi dirò esattamente cosa è cambiato — dovreste capirlo da soli. Ma le modifiche hanno semplificato alcuni momenti della logica di codifica.

Pertanto, il nostro indicatore fantasma avrà ora un proprio indice:

#define def_IndicatorGhost      2

Per questo motivo, anche la modellazione del nome è cambiata:

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

Sembra una cosa da poco, ma presto cambierà molto. Continuiamo.

Ora le macro della posizione dei prezzi sono sempre dritte, non ci sono più duplicazioni, quindi il nostro codice ora si presenta come segue:

#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)

Queste modifiche ci hanno costretto a creare altre due funzioni, ora ne mostrerò una e in seguito l'altra. La prima è la sostituzione della funzione che crea gli indicatori stessi. Ha letteralmente chiarito cosa rende davvero un indicatore diverso da un altro. Lo si può vedere qui sotto:

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Avrete notato che amo usare le direttive per il preprocessore nel mio codice. Lo faccio quasi sempre. Tuttavia, come si può vedere, ora è abbastanza facile differenziare gli indicatori. Se si desidera dare all'indicatore i colori desiderati, modificare questo codice. Poiché sono tutti quasi identici, utilizzando una macro possiamo farli funzionare tutti allo stesso modo e con gli stessi elementi. Questo è un riutilizzo definitivo del codice.

Esiste un'altra funzione con un nome molto simile a questo. Ma fa qualcosa di diverso, ne parlerò in dettaglio alla fine.

La funzione IndicatorAdd è stata modificata — abbiamo eliminato alcuni frammenti.

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);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

Uno dei frammenti rimossi è stato sostituito con quello evidenziato. Significa che gli ordini pendenti e gli indicatori 0 non saranno più creati? Sono ancora creati, ma in un luogo diverso. Quindi, è prevista un'altra funzione.

Eccola qui — la funzione che crea gli indicatori di ordini pendenti e l'indicatore 0. Il codice di UpdateIndicators è il seguente:

#define macroUpdate(A, B) { if (B > 0) {                                                                \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

La funzione ha un controllo molto interessante che viene evidenziato nel codice. Contribuirà alla creazione di indicatori fantasma, per cui la funzione IndicatorAdd non sarà più in grado di creare indicatori di ordini pendenti e l’indicatore 0. Ma questo controllo non è sufficiente per creare un indicatore fantasma.

La funzione DispatchMessage ora include alcuni dettagli; si tratta di piccole modifiche, ma che rendono la nostra vita molto più semplice. Mostrerò le parti che sono cambiate:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Code ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Code ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Code ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

CHARTEVENT_MOUSE_MOVE ha una parte modificata. Questo codice controlla se stiamo lavorando con il fantasma. Se si tratta di un fantasma, il frammento viene bloccato. Altrimenti, il movimento è possibile (a condizione che l'indicatore stesso possa muoversi).

Non appena si fa clic sulla nuova posizione dell'indicatore, il fantasma con tutti i suoi componenti sarà rimosso dall'elenco degli oggetti. Penso che dovrebbe essere chiaro. Ora prestate attenzione al punto evidenziato — si tratta della chiamata alla funzione CreateGhostndicator. Discuteremo questo codice nella prossima sezione.


2.0.6. Come funziona CreateGhostIndicator

CreateGhostIndicator sembra una funzione strana. Vediamo di seguito il suo codice:

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

È molto interessante che in questa funzione non venga creato nulla. Tuttavia, se l'EA viene compilato ed eseguito, creerà dei fantasmi che mostreranno lo stato dell'ordine sul server. Per capirlo, guardate il seguente video. Questa è una dimostrazione di come funziona il sistema nella realtà.



Gli indicatori fantasma vengono realmente creati sul grafico, ma come avviene questo processo? Come siamo riusciti a creare gli indicatori senza crearli effettivamente da qualche parte nel codice?

Questi sono fantasmi. Non si vedrà in realtà che vengono creati, non ha senso leggere il codice cercando di trovare la riga che dice: "QUI... Ho scoperto che... in questo punto vengono creati degli indicatori fantasma...". La verità è che sono semplicemente già presenti sul grafico, ma non vengono visualizzati da nessuna parte finché non iniziamo a manipolare l'ordine o la posizione — solo allora diventano visibili. Come è possibile?

Per capirlo, consideriamo il thread di esecuzione dell'EA.

Dopo l'inizializzazione dell’EA, vediamo il seguente thread di esecuzione:

Thread 1

init_ea <<<< Thread di inizializzazione del sistema

L'area arancione fa parte dell'EA e l'area verde fa parte della classe C_IndicatorTradeView. Guarda cosa succede prima che l'indicatore venga creato e visualizzato sullo schermo. Le frecce nere rappresentano il percorso comune degli ordini pendenti e delle posizioni aperte; la freccia blu è il percorso delle posizioni e le frecce viola mostrano il percorso degli ordini pendenti per creare i loro indicatori. Naturalmente, all'interno delle funzioni ci sono cose che indirizzano il thread in un modo o nell'altro, ma il diagramma qui riportato ha lo scopo di mostrare come funziona il tutto in termini generali.

Lo schema precedente viene utilizzato una sola volta e solo all'avvio del sistema. Ora, ogni volta che stiamo per piazzare un ordine pendente sul grafico, si avranno due diversi thread di esecuzione: il primo è responsabile della creazione dell'indicatore 0 e del tentativo di piazzare l'ordine sul grafico. Questo è mostrato nella figura seguente:

Thread 2

     <<<< Thread di inizializzazione dell'indicatore 0

Nota che non è la classe a creare l'ordine che appare sul grafico. Cercherà solo di farlo. Se tutto va bene, la funzione SetPriceSelection verrà eseguita con successo e verrà creato un nuovo thread che presenterà l'ordine sul grafico. Si otterrà così il seguente thread. L'ordine verrà effettivamente piazzato nel luogo segnalato dal server di trade, quindi non ha senso aspettare che l'ordine finisca effettivamente nel luogo specificato in origine. Se la volatilità fa sì che il server esegua l'ordine in un punto diverso da quello indicato, l'EA lo correggerà e presenterà l'ordine nel punto corretto. Quindi, dovrete solo analizzare se le condizioni sono adatte al vostro modello di trading.

Thread 3

     <<< Thread piazzamento ordine pendente

Questa è solo la parte responsabile dell'inserimento dell'ordine sul grafico. Qui intendo un ordine completo, che avrà un punto di ingresso, un take profit e uno stop loss. Ma cosa succede se uno degli ordini limite, sia esso il take profit o lo stop loss, viene rimosso dall'ordine? Questi thread non rispondono a questo. In effetti, il thread sarà molto diverso da quello qui, ma gli elementi saranno pressochè gli stessi. Vediamo di seguito come sarà il flusso se si fa clic sul pulsante per chiudere uno degli ordini limite.

Può sembrare una cosa strana.

Thread 4

     <<< Eliminazione di un ordine o dei livelli di stop

Abbiamo due thread, uno accanto all'altro. Quello contrassegnato dalla freccia viola verrà eseguito per primo. Non appena viene eseguito, l'evento OnTradeTransaction acquisisce la risposta dal server e attiverà il sistema per rimuovere l'indicatore dallo schermo. C'è solo una differenza tra l'eliminazione degli ordini di stop e la chiusura di una posizione o di un ordine: in questi casi, la funzione SetPriceSelection non verrà eseguita, ma il flusso di eventi OnTradeTransaction rimarrà.

Tutto questo è meraviglioso, ma ancora non risponde alla domanda su come appaiono i fantasmi.

Per capire come vengono creati i fantasmi, dobbiamo sapere come avviene il thread di esecuzione: come l'EA piazza un ordine pendente o come avviene in pratica la creazione dell'indicatore 0. Questo flusso è mostrato nella figura precedente. Se si comprendono i thread di esecuzione, sarà più facile comprendere i fantasmi.

Vediamo finalmente come vengono creati i fantasmi. Guardate di nuovo la funzione CreateGhostIndicator. Non crea nulla, ma manipola semplicemente alcuni dati. Perché? Perché se si cerca di creare un oggetto, questo verrà sovrapposto agli oggetti esistenti e renderizzato su di essi. In questo modo, gli oggetti richiesti saranno nascosti. Esistono due soluzioni a questo problema. Il primo è quello di creare un set inferiore a tutti gli altri. Verrà creato prima di qualsiasi altro oggetto che rappresenti gli ordini. Ma questa soluzione presenta un problema. Avremo molti oggetti inutili. Ma stiamo modificando l'intero codice per evitare questo problema. La seconda soluzione consiste nel creare un fantasma, quindi cancellare il puntatore che stiamo manipolando e poi crearlo di nuovo. Nessuna di queste soluzioni è molto pratica, inoltre entrambe sono piuttosto costose.

Studiando la documentazione, ho trovato un'informazione che ha attirato la mia attenzione: la funzione ObjectSetString consente di manipolare la proprietà dell'oggetto che a prima vista non ha senso — OBJPROP_NAME. Mi ha incuriosito il motivo per cui questo è permesso. Non ha senso. Se l'oggetto è già stato creato, che senso ha cambiare il suo nome?

Il punto è questo. Quando si rinomina un oggetto, il vecchio oggetto cessa di esistere e riceve un nuovo nome. Dopo la ridenominazione, l'oggetto prende il posto dell'oggetto originale, quindi l'EA può creare l'oggetto originale senza problemi e il fantasma può apparire e scomparire senza effetti collaterali per la grafica e senza lasciare tracce. L'unico oggetto da rimuovere è il pulsante di chiusura dell'indicatore. Ciò avviene in questa linea:

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

C'è un piccolo dettaglio qui. Nella documentazione della funzione ObjectSetString è presente un'avvertenza sul suo funzionamento:

Quando si rinomina un oggetto grafico, vengono generati contemporaneamente due eventi. Questi eventi possono essere elaborati nell'EA o nell'indicatore utilizzando la funzione OnChartEvent():

  • evento di cancellazione di un oggetto con un vecchio nome
  • evento di creazione di un oggetto con un nuovo nome

Questo è un aspetto importante da considerare, perché non vogliamo che l'oggetto che si sta per rinominare venga visualizzato se non si è pronti. Quindi, aggiungiamo un'altra cosa prima e dopo il cambio del nome:

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Secure code...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

Qualsiasi cosa all'interno del codice non attiverà gli eventi di creazione e cancellazione degli oggetti. Ora abbiamo il codice completo dove appariranno i fantasmi e avremo il comportamento corretto.

Forse non è ancora chiaro come il codice crei effettivamente dei fantasmi semplicemente rinominando l'indicatore. Vi lascio qui. Per aiutarvi un pò, vi mostrerò come si presenta il thread di esecuzione del fantasma. Questo è mostrato nell'immagine seguente:

Thread 5

    <<<< Thread del fantasma

Notare che questo è un clone quasi perfetto del thread 2, quindi potete già divertirvi a sapere come vengono creati e distrutti i fantasmi, ma senza scrivere alcun codice di creazione.


Conclusioni

Essendo un autore, ho trovato questo articolo molto interessante e persino eccitante. Beh, abbiamo dovuto modificare il codice EA un bel po'. Ma tutto questo è per il meglio. Ci sono ancora alcune cose e passaggi da compiere per renderlo ancora più affidabile. Tuttavia, le modifiche già implementate porteranno grandi benefici all'intero sistema. Vorrei sottolineare che un programma ben progettato di solito passa attraverso alcune fasi che sono state implementate qui: lo studio della documentazione, l'analisi dei thread di esecuzione, l’analisi comparativa del sistema per vedere se si sovraccarica nei momenti critici e soprattutto la calma, per non trasformare il codice in un vero mostro. È molto importante evitare di trasformare il nostro codice in una copia di Frankenstein, perché questo non migliorerà il codice, ma renderà solo più difficili i futuri miglioramenti e soprattutto le correzioni.

Un caloroso abbraccio a tutti coloro che seguono questa serie. Spero di vedervi nel prossimo articolo, perché non abbiamo ancora finito e c'è ancora molto da fare.



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

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.
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)
In questo articolo renderemo il sistema più affidabile per garantire un utilizzo robusto e sicuro. 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 uno dei modi. Un altro è usare l'OOP.
Matrici e vettori in MQL5: Funzioni di attivazione Matrici e vettori in MQL5: Funzioni di attivazione
Qui descriveremo solo uno degli aspetti dell'apprendimento automatico - le funzioni di attivazione. Nelle reti neurali artificiali, una funzione di attivazione del neurone calcola il valore di un segnale di output in base ai valori di un segnale di input o di un insieme di segnali di input. Ci addentreremo nei meccanismi interni del processo.
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.