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

Sviluppare un Expert Advisor per il trading da zero (Parte 26): Verso il futuro (I)

MetaTrader 5Esempi | 14 febbraio 2024, 11:32
317 0
Daniel Jose
Daniel Jose

Introduzione

Nonostante le correzioni e i miglioramenti del codice illustrati negli articoli della Parte 24 e della Parte 25 della serie "Sviluppare un Expert Advisor per il trading da zero", in cui abbiamo visto come aumentare la robustezza del sistema, rimanevano ancora alcuni dettagli. Ma non perché fossero meno rilevanti, anzi, sono davvero importanti.

Ora abbiamo alcune domande che riguardano il modo in cui vogliamo lavorare e le cose che facciamo durante la giornata di trading. Molti trader si limitano a piazzare un ordine a un certo prezzo e non lo spostano da quel punto. Qualunque cosa accada, assumeranno che questo sia il punto di ingresso perfetto e non sposterà l'ordine. Possono spostare i livelli di stop o addirittura eliminarli, ma non cambiano il punto di ingresso.

Pertanto, i rimanenti difetti del codice non influenzeranno su come lavorano effettivamente i trader. Potrebbero anche rendersi conto che il sistema degli ordini contiene dei difetti (ad esempio, quelli che correggeremo in questo articolo). Ma coloro che amano inseguire il prezzo, cercando di entrare comunque in un trade, ma non vogliono entrare nel mercato, saranno testimoni di molti errori nel sistema. Alcuni di essi possono interferire e rendere le transazioni poco sicure (per usare un eufemismo), mentre altri guadagneranno, lasciando questi trader indifesi di fronte al mercato.


2.0. Implementazione

Per iniziare il nostro viaggio in questo articolo, cominciamo a correggere un difetto che rende l'EA un vero e proprio "tritasoldi". Ancora una volta, se non cambiate continuamente il punto di ingresso, questo problema non vi riguarderà. Tuttavia, vi consiglio di pensare ad aggiornare il vostro codice, non si sa mai. Anche se la correzione sarà già implementata nel codice allegato, si potrebbe pensare che questo danneggerà l'EA, perché perderà un po' di prestazioni, il che è vero. Tuttavia, cosa è meglio: perdere un po' di performance o rischiare di perdere denaro con un'entrata sbagliata?


2.0.1. Errore del punto di ingresso

Questo errore è il primo che risolveremo, anche se tutti devono essere corretti in un modo o nell'altro. Tuttavia, questo è di gran lunga il più catastrofico di tutti. Ciò accade quando si inserisce un ordine pendente, ad esempio BUY STOP, e si sposta il punto di ingresso in modo che l'ordine sia ora di tipo BUY LIMIT. Sembra che non ci siano problemi, ma questa falla è piuttosto catastrofica, poiché l'EA nell'attuale fase di sviluppo non sarà in grado di apportare la modifica nel modo corretto. Infatti, molti EA vogliono fare questa modifica, e se questo accade, vedrai le informazioni sul grafico, ma il server avrà altre informazioni. Il sistema sarà aggiornato correttamente solo quando la posizione sarà aperta, fino a quel momento i dati tra ciò che l'EA mostra sul grafico e ciò che è sul server saranno incoerenti.

In alcuni casi, abbiamo solo questa incoerenza, mentre in altri casi il problema sarà un disastro completo. Per capirlo, leggete attentamente l'articolo.

Per eliminare questo errore, abbiamo una soluzione che può passare attraverso diversi percorsi prima di essere applicata. Ma il principio di funzionamento sarà sempre lo stesso: rimuovere l'ordine dall’order book, spostarlo in una nuova posizione, cambiare il tipo di ordine e riportarlo nell’order book. Questo è ciò che dovrebbe essere fatto, ma il modo in cui viene fatto dipende dall'implementazione specifica.

Pertanto, implementeremo la soluzione più elementare, ma poiché non è l’ideale, dovremo affrontare alcuni problemi.

La soluzione è modificare la funzione qui sotto aggiungendo le righe evidenziate.

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Sebbene questa soluzione risolva parzialmente il problema, non lo risolve completamente. Ad esempio, per gli ordini BUY STOP e SELL STOP, il problema si risolve aggiungendo queste semplici linee. Ma per BUY LIMIT e STOP LIMIT, il server eseguirà immediatamente l'ordine una volta che avremo fatto clic per modificare il punto di ingresso. La cosa peggiore qui, è che entriamo in una posizione perdente. Se l'ordine è configurato come un ordine vuoto (con livelli di profitto o perdita) e il punto di Stop Loss è al di fuori dei limiti di prezzo, oltre a eseguire immediatamente l'ordine, il server lo chiuderà subito dopo, il che significa un disastro completo per il nostro conto di trading. Ecco perché i sistemi di trading sono così difficili da sviluppare. Effettuiamo diversi test su un conto demo e se tutto sembra funzionare passiamo a un conto reale, a quel punto iniziamo a perdere denaro senza sapere cosa stia realmente accadendo.

Ripeto ancora una volta: l'errore non riguarda il caso in cui un punto di ingresso viene inserito una sola volta e non viene mai modificato. Il problema si presenta quando il trader sposta il punto.

In effetti gli ordini STOP funzionano bene. Ora dobbiamo risolvere il problema degli ordini pendenti LIMIT. Anche se questo problema può sembrare facile da risolvere, c'è una cosa da capire: NON esiste una soluzione perfetta e la soluzione che funziona meglio per lo sviluppatore del sistema potrebbe non essere quella giusta per voi.

Qui di seguito mostrerò una delle possibili soluzioni a questo problema. La soluzione sarà implementata nella stessa funzione mostrata sopra. Ecco il suo nuovo codice:

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

Si procede come segue. Quando si intende modificare il punto di ingresso di un ordine pendente, si controlla se l'ordine nell’order book (Profondità di Mercato) è di tipo STOP LIMIT o BUY LIMIT. Se non lo è, il flusso di esecuzione continuerà in un altro punto del codice. In caso affermativo, si esegue un'acquisizione immediata del prezzo corrente dell'asset e si utilizzano i seguenti criteri: per un ordine BUY, si acquisisce il valore ASK corrente. Rispettivamente, BID per gli ordini Sell. Questo sostituisce il vecchio metodo che utilizzava il valore LAST, ma poiché non è utilizzato in alcuni mercati, non lo useremo come riferimento. Quindi verificare se l'ordine nell’order book viene invalidato o se viene solo modificato.

Se l'ordine è ancora valido, il sistema ignorerà il codice di convalida e passerà alla parte in cui l'ordine verrà modificato. Ma se l'ordine nella Profondità di Mercato non è valido, il sistema eseguirà il seguente codice:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

Ma il codice sopra riportato cambierà solo gli ordini SELL LIMIT e BUY LIMIT rispettivamente in SELL STOP e BUY STOP. E se volessimo riportare questi tipi a quelli originali o semplicemente impedire tale modifica?

Se non vogliamo che il sistema cambi il tipo di ordine eseguito, basta sostituire il frammento evidenziato con il codice seguente:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

Questo codice impedisce la modifica del tipo di ordine. Possiamo modificare il punto in cui un ordine pendente verrà eseguito, ma non è possibile modificare un ordine LIMIT in un ordine STOP o viceversa. Ora, se volete continuare a inseguire il prezzo e forzare l'ingresso a un certo punto, utilizzate il codice mostrato di seguito. Questo è il codice che verrà utilizzato nell'EA.

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Nota importante: Fate attenzione quando lavorate con questo codice a causa della macro ForceNewType. Questa macro contiene un'interruzione che, quando eseguita, fa sì che il codice esca dal blocco 'case'. Pertanto, è necessario fare molta attenzione quando si modifica questo blocco.

Il sistema non avrà più un errore nello spostamento del punto di ingresso, ma abbiamo altri problemi da risolvere. Vi ho mostrato il modo per correggere il problema modificando o mantenendo lo stesso tipo di ordine — scegliete quello più adatto a voi. Ricordate che ognuna di queste soluzioni ha i suoi pro e i suoi contro. Ma non entrerò nei dettagli. Mostro solo come correggere e implementare il sistema.

Il risultato di questi cambiamenti è visibile nel video seguente:



2.0.2. Prepararsi al futuro

La modifica di cui sopra risolve il problema, ma è possibile fare qualcosa in più. Qui mostrerò l'inizio di questa modifica. Guardando al sistema di ordini dell'EA, c'è ancora molto spazio per i miglioramenti. Ci sono poche modifiche necessarie, e voglio spiegarvele in modo che possiate scegliere la strada più adatta a voi, perché ogni trader ha il proprio modo di agire sul mercato. Non voglio che vi sentiate obbligati a usare il sistema che vi mostrerò. Voglio invece creare una base che permetta a chiunque di sviluppare un EA personalizzato.

Passiamo quindi al fatto successivo: a partire dalla Parte 18, ho mostrato come sviluppare un sistema di ordini facile da usare per chi fa trading su un determinato asset. Ma nella Parte 20, il sistema di ordini ha ricevuto elementi visivi, perché a un certo punto Chart Trade diventerà superfluo per il trading, poiché tutto sarà indicato dal sistema di ordini stesso, in modo da poter modificare e configurare tutto direttamente sul grafico. Per arrivare a questo punto, dobbiamo iniziare da qualche parte, e lo faremo subito.

Che ne dite di modificare il volume negoziato direttamente all'interno dell'ordine, senza dover rimuovere l'ordine dal grafico, modificare il volume in Chart Trade e poi riposizionare l'ordine sul grafico? Interessante, vero? Implementeremo subito questa funzione. È molto utile in diversi scenari, ma è necessario imparare e capire come utilizzare il sistema perché non lo troverete su nessun'altra piattaforma. Ad essere onesti, non ho mai visto un EA che abbia questa funzionalità. Vediamo cosa si può fare per avere questa funzionalità in qualsiasi EA.

Per prima cosa, definire un nuovo indice indicatore.

#define def_IndicatorFloat      3

Quando un ordine pendente riceve questo valore come ticket, può essere elaborato in modo completamente diverso. Tutto ciò che esisteva in precedenza rimarrà nel sistema di ordini, mentre noi aggiungiamo solo un nuovo indice.1

Dopodiché, aggiungeremo un nuovo oggetto al sistema:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

Questo oggetto abiliterà sempre alcune cose mentre l'ordine è pendente.

Ora passiamo alla classe C_Object_BitMap per modificarla. Aggiungere alcune definizioni:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

Dobbiamo sapere cosa succede in questa classe. Quindi, aggiungere le seguenti funzioni:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton restituisce lo stato del pulsante. MetaTrader 5 modifica lo stato, quindi non è necessario implementare passaggi aggiuntivi, ma solo verificare se il valore del pulsante è True o False. Ma può accadere che lo stato non rifletta ciò che vogliamo. Quindi utilizzare SetStateButton per impostare lo stato in modo che rifletta lo stato effettivo visto dal server di trade e dall'EA.

Un'altra semplice modifica riguarda la classe C_Object_Edit:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

Mostra se il valore può essere modificato o meno. Vogliamo poter modificare i volumi degli ordini direttamente sul grafico, senza utilizzare Chart Trade. Qualsiasi ordine pendente creato sarà sempre in modalità di sola lettura, ma creeremo un sistema che modificherà questa situazione.

Torniamo quindi a C_IndicatorTradeView e implementiamo altre modifiche. Creeremo una nuova funzione per il sistema. È la seguente:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

Quando viene richiamata questa funzione, tutti gli oggetti indicatore vengono rinominati, ovvero il valore che punta al ticket dell’ordine viene sostituito da un altro valore. In questo caso, si tratta dell'indicatore che abbiamo considerato all'inizio di questo argomento. Abbiamo ancora una domanda. Non utilizzo alcuna struttura per mantenere la lista degli oggetti indicatori, ma lo faccio in modo diverso. In questo modo lasciamo che MetaTrader 5 si occupi di questa lista per noi. Ma per questo motivo non posso creare un numero illimitato di ordini variabili, in quanto saremo limitati ad avere un solo ordine variabile. Questo può essere verificato utilizzando la seguente riga:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

Il controllo in questo caso è semplice: se la linea dell'indicatore si trova da qualche parte, la macro restituirà un valore diverso da 0, così sappiamo che esiste già un indicatore che utilizza il ticket riservato. Questo sarà importante in seguito, perché l'EA potrà ripristinare i dati dell'indicatore per il quale la richiesta è stata negata. MetaTrader 5 cambia automaticamente lo stato dell'oggetto Bitmap, quindi è necessario informare il chiamante del fallimento.

La prossima modifica necessaria è nella funzione che crea gli indicatori:

#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 : (A == IT_PENDING ? 108 : 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);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Tutte le parti evidenziate sono state aggiunte per supportare il nostro nuovo sistema. In pratica, creiamo una casella di controllo che sarà sempre impostata su true, il che significa che l'ordine sarà immediatamente inserito nell’order book. Non volevo modificare questo modo di fare trading, ma non è il semplice fatto di cambiare il valore della casella di controllo da 'true' a 'false' che impedirà che gli ordini vengano piazzati direttamente. Questa modifica richiederebbe altre modifiche ancora più profonde e il problema è che a un certo punto si potrebbe arrivare a piazzare un ordine e dimenticare di selezionare la casella di controllo. Il punto di ingresso verrebbe mancato e si penserebbe che l'EA sia difettoso, mentre in realtà è tutto dovuto a una dimenticanza. Per evitare questo problema, per impostazione predefinita gli ordini pendenti andranno direttamente all’order book, per cui è necessario modificare esplicitamente il loro stato.

La prossima funzione veramente importante è mostrata di seguito:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

Le parti di codice evidenziate fanno una cosa interessante: aggiornano solo i valori che saranno utilizzati nel selettore, ma questi valori sono effettivamente memorizzati nell'indicatore stesso. Può anche succedere che si stia spostando il sistema in modo più generale, quindi è necessario specificare questi valori nel selettore, in modo che la funzione che esegue i calcoli di posizionamento specifichi i valori corretti.

C'è qualcosa in questa funzione che potrebbe non avere senso. È responsabile della creazione e della modifica dei dati di un ordine pendente, ma se la si osserva, non si nota alcun punto in cui l'ordine pendente verrà reinviato all’order book. È possibile spostare, modificare e regolare il valore del volume dell'ordine direttamente sul grafico, ma non sarà possibile vedere come tornerà sul grafico.

È un dato di fatto. L'intero sistema di modifica e creazione degli ordini pendenti è implementato nella funzione sopra. Stranamente, questa funzione non inserisce nuovamente l'ordine nell’order book solo perché lo vogliamo noi, ma perché effettua una richiesta, come mostrato di seguito. Per non complicare, mostrerò solo la parte responsabile di una richiesta di inserimento dell'ordine nella Profondità di Mercato.

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

// ... Internal code...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else 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_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

// ... Rest of the code...

Osservate come il sistema si costruisce da solo: programmiamo sempre meno mentre il sistema diventa sempre più grande.

Il codice evidenziato ha a che fare con l'indicatore creato all'inizio dell'argomento. Anche se tutto sembra funzionare bene, ci sono alcune cose che verranno modificate in seguito perché quando l'ordine variabile tornerà nell’order book, avrà lo svantaggio di essere un ordine di day trading, quindi verrà chiuso alla fine della giornata. Verrà modificato in seguito, ma è bene che ne siate consapevoli. Ora potreste essere confusi da tutto questo e non comprendere ancora come l'ordine pendente entri ed esca effettivamente dall’order book, quando facciamo clic sulla casella di controllo. Guardate il diagramma seguente:

Assicuratevi che tutte le chiamate provengano dallo stesso punto. Abbiamo rimosso un ordine dalla Profondità di Mercato, ma continuerà ad essere presente sul grafico. Tutte le manipolazioni vengono eseguite come mostrato negli articoli precedenti. Ma se si cerca di individuare un momento specifico in cui l'ordine tornerà alla Profondità di Mercato, ci si può perdere nel codice. Ora, se si osserva il diagramma, si può notare che la chiamata proviene dalla funzione DispatchMessage, perché è l'unico punto in cui viene chiamata la funzione SetPriceSelection. Ma se osserviamo la funzione SetPriceSelection, non c'è alcun riferimento alla creazione di un ordine con l'indice utilizzato nel sistema variabile. Ma fate attenzione a una cosa. Abbiamo la creazione dell'ordine in base all'indice 0 e questo è esattamente quello che utilizziamo. Cambiamo il ticket dell'ordine e informiamo che sarà il ticket dell'indice 0 — in questo modo l'ordine verrà creato. Guarda il codice di seguito per comprendere come funziona.

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

Il codice è perfetto, tranne che per la riga evidenziata. Attualmente non c'è modo di risolvere questo problema. Questo verrà fatto nel prossimo articolo, poiché dovremo apportare alcune modifiche alla classe stessa.

Il video qui sotto mostra il risultato delle modifiche. Presta attenzione a come viene modificato il volume e come viene inviato un nuovo ordine al punto specificato. L'EA è ora molto più facile da usare.



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

Sviluppare un Expert Advisor per il trading da zero (Parte 27): Verso il futuro (II) Sviluppare un Expert Advisor per il trading da zero (Parte 27): Verso il futuro (II)
Passiamo ad un sistema di ordini più completo direttamente sul grafico. In questo articolo mostrerò un modo per correggere il sistema degli ordini, o meglio, per renderlo più intuitivo.
Impara come progettare un sistema di trading tramite Fractals Impara come progettare un sistema di trading tramite Fractals
Ecco un nuovo articolo della nostra serie su come progettare un sistema di trading basato sugli indicatori tecnici più popolari. Impareremo a conoscere un nuovo indicatore, l'indicatore Fractals e a progettare un sistema di trading basato su di esso, da eseguire nel terminale MetaTrader 5.
Sviluppare un Expert Advisor per il trading da zero (Parte 28): Verso il futuro (III) Sviluppare un Expert Advisor per il trading da zero (Parte 28): Verso il futuro (III)
C'è ancora un compito per la quale il nostro sistema di ordini non è all'altezza, ma FINALMENTE lo scopriremo. MetaTrader 5 fornisce un sistema di ticket che consente di creare e correggere i valori degli ordini. L’idea è quella di avere un Expert Advisor che renda lo stesso sistema di ticket più veloce ed efficiente.
Algoritmi di ottimizzazione della popolazione: Ottimizzazione delle Piante Infestanti (Invasive Weed Optimization - IWO) Algoritmi di ottimizzazione della popolazione: Ottimizzazione delle Piante Infestanti (Invasive Weed Optimization - IWO)
La sorprendente abilità delle piante infestanti di sopravvivere in un'ampia varietà di condizioni è diventata l'idea per un potente algoritmo di ottimizzazione. IWO è uno dei migliori algoritmi tra quelli esaminati precedentemente.