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

Sviluppare un Expert Advisor per il trading da zero (Parte 21): Nuovo sistema di ordini (IV)

MetaTrader 5Sistemi di trading | 3 agosto 2023, 10:49
154 0
Daniel Jose
Daniel Jose

Introduzione

Nel precedente articolo, Sviluppare da zero un Expert Advisor per il trading da zero (Parte 20), abbiamo considerato le principali modifiche che dovevano essere apportate per ottenere un sistema visivo degli ordini. Tuttavia, i passaggi successivi richiedevano ulteriori spiegazioni, così ho deciso di dividere l'articolo in più parti. Qui finiremo di apportare le modifiche principali. Ce ne saranno parecchie, ma sono tutte necessarie. Bene, l'intero lavoro sarà piuttosto interessante. Tuttavia, non completerò il lavoro qui, perché ho lasciato ancora qualcosa da fare per completare davvero il sistema. Ad ogni modo, alla fine di questo articolo, il sistema avrà quasi tutte le funzionalità necessarie.

Passiamo direttamente all'implementazione.


1.0. Implementazione

Prima di tutto, aggiungiamo un pulsante per Chiudere o Cancellare l'ordine. La classe responsabile dei pulsanti è mostrata di seguito.

1.0.1. Classe C_Object_BtnBitMap

Questa classe è responsabile del supporto dei pulsanti bitmap sul grafico, come puoi vedere di seguito.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_BtnClose    "Images\\NanoEA-SIMD\\Btn_Close.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
		void Create(string szObjectName, string szResource1, string szResource2 = NULL)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
                bool GetStateButton(string szObjectName) const
                        {
                                return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
                        }
//+------------------------------------------------------------------+
};


Durante la scrittura del codice di questa classe, mi sono reso conto che la classe di posizionamento poteva essere spostata nella classe C_Object_Base. L'intera classe C_Object_BackGround eliminerebbe questo codice in quanto apparterrebbe ad una classe inferiore. Questo è noto come riutilizzo del codice. Questo approccio comporta meno programmazione, aumenta le prestazioni, ma soprattutto il codice diventa più stabile in quanto le modifiche vengono controllate più frequentemente.

Per aggiungere un pulsante CHIUDI, faremo quanto segue:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_TradeLine.mqh"
#include "C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
class C_ObjectsTrade
{

// ... Class code ...

}

Il prossimo passaggio

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};

Passo successivo

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;


Passo successivo

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2;
                                string sz0;
                                

// ... Internal function code ...

                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                                m_BackGround.Size(sz0, 92, 22);
                                                break;
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 110, 22);
                                                break;
                                }
                                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }


Passo successivo

#define macroDelete(A)  {                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));         \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));        \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


E infine, l'ultimo passaggio...

#define macroSetAxleY(A)        {                                               \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);    \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\
                                }
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {

// ... Internal code...
                                
                        }
#undef macroSetAxleX
#undef macroSetAxleY


Quando esegui questo sistema, otterrai il seguente risultato:


Ma questo pulsante ancora non funziona, sebbene MetaTrader 5 generi un evento affinché il pulsante venga elaborato dall'Expert Advisor. Non abbiamo ancora implementato questa funzionalità. Ci torneremo un po' più avanti in questo articolo.


1.0.2. La classe C_Object_Edit

Il sistema sarebbe inutile se non potesse informare il trader sui valori scambiati. Per questa finalità, abbiamo la classe C_Object_Edit. La classe dovrà subire alcune modifiche in seguito per aumentarne la funzionalità, ma per ora la lasceremo così com'è: informerà il trader su cosa sta succedendo. Per implementare ciò, dobbiamo aggiungere alcune righe di codice nella classe. Il primo frammento che contiene il nuovo codice:

void Create(string szObjectName, color cor, int InfoValue)
{
        C_Object_Base::Create(szObjectName, OBJ_EDIT);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
        SetTextValue(szObjectName, InfoValue, cor);
}

Il codice evidenziato evita che i valori vengano modificati dal trader, ma come ho detto, questo cambierà in futuro. Ciò richiederà alcune altre modifiche che al momento non sono rilevanti.

La seguente funzione determina la visualizzazione del testo. Presta attenzione ad un dettaglio in questa funzione:

void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE)
{
        color clr;
        clr = (cor != clrNONE ? cor  : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive));
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue));
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr);
}

Il codice selezionato mostrerà il colore dello sfondo del testo in base al valore inserito. È importante che lo facciamo qui, poiché non vogliamo costantemente cercare di indovinare se un valore è negativo o positivo, o passare il tempo a cercare di determinare se c'è un valore negativo nel testo. È molto comodo guardare e capire immediatamente se il valore è positivo o negativo. Questo è ciò che fa il codice: ora puoi determinare istantaneamente se il valore è negativo o positivo, in base al colore. Ma c'è una condizione che richiede che il colore non deve essere stato definito in precedenza. Questo sarà utile anche in seguito.

Successivamente abbiamo l'ultima funzione di questa classe mostrata di seguito.

long GetTextValue(string szObjectName) const
{
        return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * 
                                (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1));
};


Potresti notare che quando rappresentiamo i valori, saranno sempre positivi a causa della loro formattazione. Ma quando controlliamo il contenuto di un oggetto, dobbiamo avere le informazioni corrette. Qui è dove viene utilizzato il codice evidenziato. È qui che vengono utilizzate le informazioni sul colore: se il colore indica che il valore è negativo, verrà corretto per fornire all'EA le giuste informazioni. Se il colore indica un valore positivo, il valore verrà semplicemente salvato.

Le definizioni dei colori si trovano nella classe stessa. Questi possono essere modificati se si desidera impostare altri colori in un secondo momento, ma assicurarsi di utilizzare colori diversi in modo che la funzione precedente funzioni correttamente, altrimenti l'EA otterrà valori ambigui. Pertanto, l'EA potrebbe vedere i valori negativi come positivi, il che causerà problemi all'intera analisi eseguita dall'EA.


1.0.3. La classe C_Object_Label

Questa è l'ultima classe di cui abbiamo bisogno in questa fase. In realtà, stavo pensando di non creare questa classe, poiché le sue azioni sono simili alla classe C_Object_BtnBitMap. Ma siccome volevo essere in grado di aggiungere informazioni testuali indipendentemente dalla classe C_Object_Edit, ho deciso di creare una nuova classe qui.

Il suo codice è super semplice, vedi sotto.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Edit.mqh"
//+------------------------------------------------------------------+
class C_Object_Label : public C_Object_Edit
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        };
//+------------------------------------------------------------------+
};


Non abbiamo bisogno di nient'altro in questa classe, poiché tutto il resto del lavoro è già stato implementato da oggetti di classe inferiore.

Come puoi vedere, OOP è uno strumento molto potente. Più organizziamo il codice in classi, meno abbiamo bisogno di programmare classi simili tra loro.

Ma c'è un piccola modifica che dobbiamo implementare. Durante la sperimentazione, ho notato che è molto difficile interpretare i dati del pannello, quindi l'ho modificato come segue.

    

In questo modo è più facile visualizzare valori grandi. Questo è come apparirà l'oggetto risultante: la parte superiore mostra il numero di contratti o il fattore di leva della posizione aperta, mentre la parte inferiore mostra il risultato della posizione.

Per implementare ciò, dobbiamo modificare la classe C_Object_Base che è la principale responsabile del posizionamento degli oggetti. Le modifiche sono evidenziate nel codice sottostante.

virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };


Dopo, possiamo passare al passaggio successivo: modificare la classe C_ObjectsTrade.


1.0.4 La classe C_ObjectsTrade

Ora completiamo il disegno degli oggetti richiesti dopodiché riusciremo realmente ad ottenere il risultato desiderato sul grafico: avremo tutte le informazioni presentate e tutti i suoi oggetti collegati. Non è difficile, analizzeremo le azioni passo-passo. Se capisci come è fatto, potrai aggiungere qualsiasi altra informazione desideri semplicemente seguendo le istruzioni e tutto andrà bene. La prima cosa che dobbiamo fare è definire i nuovi eventi a cui gli oggetti dovrebbero rispondere. Sono evidenziati nel codice sottostante.

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};

Ora aggiungiamo gli oggetti richiesti. Anche i nuovi oggetti sono evidenziati nel codice sottostante:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;
C_Object_Edit           m_EditInfo,
                        m_InfoVol;
C_Object_Label          m_BtnMove;


Successivamente, creiamo gli oggetti e definiamo come appariranno sullo schermo. Notare che gli oggetti devono essere creati nello stesso ordine in cui dovrebbero apparire: prima l'oggetto sullo sfondo, poi l'oggetto successivo da posizionare sullo sfondo e così via, fino a quando non vengono creati tutti gli oggetti. Se lo fai in un ordine sbagliato e uno degli oggetti è nascosto, cambia semplicemente la sua posizione in questo codice. Ora, ecco il codice:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        int infoValue;
                                
        switch (it)
        {
                case IT_TAKE    :
                        infoValue = m_BaseFinance.FinanceTake;
                        cor1 = clrForestGreen;
                        cor2 = clrDarkGreen;
                        cor3 = clrNONE;
                        break;
                case IT_STOP    :
                        infoValue = - m_BaseFinance.FinanceStop;
                        cor1 = clrFireBrick;
                        cor2 = clrMaroon;
                        cor3 = clrNONE;
                        break;
                case IT_PENDING:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrCornflowerBlue;
                        cor2 = clrDarkGoldenrod;
                        cor3 = clrLightBlue;
                        break;
                case IT_RESULT  :
                default:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrDarkBlue;
                        cor2 = clrDarkBlue;
                        cor3 = clrSilver;
                        break;
                }                               
                m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2);
                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1);
                switch (it)
                {
                        case IT_TAKE:
                        case IT_STOP:
                        case IT_PENDING:
                                m_BackGround.Size(sz0, 92, 22);
                                break;
                        case IT_RESULT:
                                m_BackGround.Size(sz0, 84, 34);
                                break;
                }
                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue);
                m_EditInfo.Size(sz0, 60, 14);
                if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                else
                {
                        m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue);
                        m_InfoVol.Size(sz0, 60, 14);
                }
}

Tutte le righe evidenziate sono le aggiunte al codice fatte fin dall'ultima versione presentata nel precedente articolo. Ora possiamo scrivere il codice per la seguente funzione.

#define macroDelete(A)  {                                                                       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));                 \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));                \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT));                 \
                if (A != IT_RESULT)                                                             \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE));         \
                else                                                                            \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME));       \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


Nota il vantaggio di utilizzare la macro: ho dovuto aggiungere solo le parti evidenziate in modo da poter eliminare tutti gli oggetti del pannello. Ora stiamo usando 6 oggetti in 4 pannelli. Se questo fosse implementato in modo diverso, ciò richiederebbe troppo lavoro e quindi avrebbe un'alta probabilità di errori. Finiamo la funzione di posizionamento.

#define macroSetAxleY(A)        {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y);                         \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);                            \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);                            \
                m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));  \
                if (A != IT_RESULT)                                                                     \
                        m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y);                      \
                else                                                                                    \
                        m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1);                 \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B);                 \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);                    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);                \
                m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21);                \
                if (A != IT_RESULT)                                                             \
                        m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80);         \
                else                                                                            \
                        m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21);       \
                                }
                                                                                
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {
                                double ad;
                                int x, y;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetAxleY(it);
                                macroSetAxleX(it, m_PositionMinimalAlxeX);
                                if (Leverange == 0) return;
                                if (it == IT_PENDING)
                                {
                                        ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
                                        macroSetAxleY(IT_TAKE);
                                        macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110);
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
                                        macroSetAxleY(IT_STOP);
                                        macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220);
                                }
                        }
#undef macroSetAxleX
#undef macroSetAxleY


Ancora una volta, è stato aggiunto pochissimo codice. Anche così, la funzione può funzionare con tutti gli elementi perché li posiziona correttamente grazie all'uso di macro. Dopo aver compilato l'EA in questa fase, otteniamo il seguente risultato:


Anche se tutto sembra carino, questi controlli continuano a non funzionare: dobbiamo implementare gli eventi per ciascun oggetto. Senza gli eventi questa interfaccia è quasi inutile, poiché l'unica cosa che effettivamente farà, sarà sostituire le righe utilizzate originariamente.


2.0. Come risolvere il problema

Se fosse semplice, tutti potrebbero farlo. Ma abbiamo sempre problemi da risolvere, e questo fa parte del processo di sviluppo. Potrei solo fornire la soluzione invece di mostrare come risolvere. Ma voglio che questi articoli ti motivino ad affrontare i problemi e ad imparare come programmare effettivamente. Quindi ci sarà qualcosa di interessante in questa sezione.


2.0.1. Aggiustare le cose man mano che il grafico si aggiorna

Questo è il primo problema che abbiamo. È causato dal fatto che le posizioni degli oggetti non sono aggiornate insieme all'aggiornamento del grafico. Per capirlo, dai un'occhiata alla gif qui sotto:

Cose come questa possono essere esasperanti, ma la soluzione è molto semplice: MetaTrader 5 stesso genera un evento notificando che il grafico deve essere aggiornato. Quindi, quello che dobbiamo fare è acquisire l'evento e aggiornare il nostro sistema di ordini.

La modifica deve essere acquisita alla chiamata all'evento CHARTEVENT_CHART_CHANGE. L'aggiornamento è più semplice se si usa la funzione UpdatePosition, presente nel codice dell’EA. Tutto quello che dobbiamo fare è aggiungere solo una riga al nostro codice. Questo viene fatto in C_OrderView come mostrato di seguito:

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

// ... Code ....
                                
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

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

Questa semplice soluzione ha un problema: se hai molti ordini per un asset, potrebbe volerci del tempo, a causa del quale l'EA può rimanere bloccato con l'aggiornamento prima di poter passare a un'ulteriore elaborazione. Esistono altre soluzioni più complicate che accelerano il processo. Ma questa soluzione è più che sufficiente per questo sistema. Il risultato è il seguente.


Tutto sembra essere corretto, vero? Ma qui c'è un errore. È difficile vederlo finché non testiamo il sistema. Ma c'è davvero un difetto in questo sistema che, anche dopo averlo corretto, non va da nessuna parte.


2.0.2. EA, interrompere la selezione automatica degli elementi

Se guardi la gif sopra, noterai che la linea di stop è selezionata, anche se non l'abbiamo selezionata. L'EA lo fa ogni volta. A seconda delle impostazioni al momento della creazione del pannello, può succedere che la RA selezioni il take profit o la linea della posizione, e così via ogni volta che il grafico viene spostato.

Puoi impazzire cercando di capire cosa sta succedendo, ma la soluzione è ancora più semplice della precedente: basta aggiungere una riga allo stesso codice e l'EA smetterà automaticamente di selezionare una riga. La correzione è evidenziata nel codice seguente.

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        double infoValue;

// ... Internal code...

        Select(NULL);
}

Queste cose accadranno sempre durante lo sviluppo. Intendo i bug, quelli facili da individuare e quelli meno evidenti, che a volte non notiamo all'inizio. Comunque possono succedere. Pertanto, l'apprendimento della programmazione è un processo continuo. A volte puoi risolvere tu stesso questi problemi e condividerli con tutti per aiutarli a risolvere lo stesso problema. Prova a farlo. Personalmente lo faccio in modo pratico, perché è così che ho imparato a programmare. Allo stesso modo puoi anche imparare a creare programmi. In generale, è parte dell'apprendimento ottenere un codice sorgente e modificarlo. Dovresti farlo con un codice funzionale, così è più semplice comprendere com’è stato costruito, e questo spesso porta buoni frutti, in quanto impariamo molto capendo come ogni programmatore è riuscito a risolvere uno specifico problema.

Beh, ma questo era solo qualcosa per motivarti a studiare. Passiamo a qualcosa di veramente importante.


3.0. Gestione degli eventi

Costruiremo un sistema che riporterà il risultato di una posizione. Questo sostituirà l'area relativa in Chart Trade, ma lascerò Chart Trade così com'è, perché indica il risultato totale delle posizioni. Se un conto supporta l’hedging, il valore sarà diverso da quello specificato nel sistema di ordini, poiché uno è un valore locale e un altro rappresenta il valore totale. Non ci sarà tale differenza su altri tipi di account, quindi se preferisci, puoi rimuovere il sistema dei risultati da Chart Trade.

3.0.1. Visualizzare il risultato della posizione

Coloro che vedono il codice per la prima volta possono perdersi, non sapendo dove cercare le informazioni, e possono creare altre operazioni per fare qualcosa che già fa il codice originale. Questo è ciò che di solito porta a molti problemi - potremmo generare codice extra che genera bug extra che il codice originale non aveva. Questo non è conforme alla regola REUSE, secondo la quale si dovrebbe programmare solo quando è veramente necessario. Quindi, sapendo come funziona MetaTrader 5 e sapendo come funziona già l'EA, dovresti trovare il posto in cui viene generato il risultato mostrato in Chart Trade. Perché se viene presentato il risultato delle posizioni, dovremmo usarlo. Presta attenzione al codice qui sotto.

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
        TimesAndTrade.Update();
}


Successivamente, passiamo al punto evidenziato. Il codice sorgente è mostrato di seguito.

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


Ebbene, si potrebbe pensare: ma come prenderò i dati da lì e li applicherò al pannello, se non c'è modo per sapere a quali oggetti dovrei fare riferimento? Può sembrare che gli oggetti siano stati creati liberi, senza alcun criterio o cura... Beh, questo non è vero. Se pensi o immagini le cose in questo modo, sarebbe meglio iniziare a imparare un po' di più su come funziona effettivamente la piattaforma MetaTrader 5. È del tutto vero che non ho creato alcun tipo di elenco, array o struttura che faccia riferimento agli oggetti che vengono creati, ma questo è stato fatto apposta. Perché so che funziona. Ti mostrerò che funziona davvero: puoi fare riferimento a un determinato oggetto senza utilizzare alcuna struttura per memorizzare gli oggetti che saranno sul grafico. Ciò che è veramente necessario è modellare correttamente il nome dell'oggetto. E questo è quanto.

Q: Come sono stati modellati i nomi degli oggetti?

La risposta è la seguente:

1 - Sequenza dell'intestazione Questa sequenza distinguerà l'oggetto utilizzato nel sistema degli ordini da tutti gli altri oggetti.
2 - Limitazione carattere Indica che devono seguire altre informazioni.
3 - Indicazione del tipo Distingue tra Take e Stop.
4 - Limitazione carattere Lo stesso del 2.
5 - Ticket d'ordine o di posizione Ricorda il ticket dell'ordine - collega i dati dell'ordine OCO e li distingue tra gli ordini.
6 - Limitazione carattere Lo stesso del 2.
7 - Segnalazione evento Distingue tra gli oggetti all'interno dello stesso pannello

Ecco, la modellazione è tutto, anche se a chi non programma in realtà sembra che stiamo creando qualcosa di ripetitivo, in realtà stiamo creando qualcosa di unico - ognuno degli oggetti è unico e può essere referenziato attraverso una semplice regola. E questa regola è creata dal seguente codice:

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev)
                        {
                                return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev);
                        }


Pertanto, se dici al codice precedente a quale ticket d'ordine, a quale indicatore e a quale evento vuoi accedere, avrai il nome di un particolare oggetto. Ciò consente di manipolare i suoi attributi. Sapendo che questo è il primo passo, dobbiamo prendere un'altra decisione: come rendere sicura questa manipolazione, senza causare scompiglio nel codice e senza trasformarlo in Frankenstein?

Facciamolo. Andiamo alla classe C_ObjectsTrade e aggiungiamo il seguente codice.

inline void SetResult(ulong ticket, double dVolume, double dResult)
                        {
                                m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult);
                                m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult);
                        }


Passiamo ora alla classe C_Router e aggiungiamo il codice evidenziato.

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

Questo risolve uno dei problemi. Ma abbiamo ancora altri problemi irrisolti.


3.0.2. Indicare il volume dell'ordine pendente

Ora risolviamo il problema del volume indicato in un ordine pendente. Per fare questo dobbiamo creare una nuova funzione nella classe C_ObjectsTrade.

inline void SetVolumePendent(ulong ticket, double dVolume)
                        {
                                m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                        }


Fatto ciò, utilizziamo la funzione UpdatePosition della classe C_Router e l'aggiornamento avverrà senza problemi.

void UpdatePosition(int iAdjust = -1)
{

// ... Internal code ....

        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
                bTest = CheckLimits(price);
                vol = OrderGetDouble(ORDER_VOLUME_CURRENT);

// ... Internal code...

                CreateIndicatorTrade(ul, price, IT_PENDING);
                SetVolumePendent(ul, vol);
                CreateIndicatorTrade(ul, take, IT_TAKE);
                CreateIndicatorTrade(ul, stop, IT_STOP);
        }
};


Così, il problema è risolto. Ora dobbiamo risolvere il problema dei valori ​​indicati come Take e Stop, perché questi valori ​non sono veri dopo aver piazzato l'ordine sul grafico.


3.0.3. Evento clic sul pulsante Chiudi Pannello

L'unico modo sicuro per rimuovere un ordine o uno dei suoi livelli di stop è il pulsante Chiudi situato nell'angolo di ciascuno dei valori. Ma qui abbiamo un evento implementato in modo errato. Risolviamo questo problema.

L'evento click dovrebbe effettivamente essere implementato nella classe C_OrderView. Sostituiamo il vecchio sistema con il codice evidenziato.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   	ticket;
        double  	price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType 	ev;
                                
        switch (id)
        {

// ... Internal code...
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetInfosOrder(sparam, ticket, price, it, ev))
                        {
                                switch (ev)
                                {
                                        case EV_CLOSE:
                                                if (OrderSelect(ticket)) switch (it)
                                                {
                                                        case IT_PENDING:
                                                                RemoveOrderPendent(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                                break;
                                                }
                                                if (PositionSelectByTicket(ticket)) switch (it)
                                                {
                                                        case IT_RESULT:
                                                                ClosePosition(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0);
                                                                break;
                                                }
                                                break;

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


C'è un'altra cosa da aggiungere in questa classe. Cosa accadrebbe se il trader cancellasse accidentalmente un oggetto che riporta i dati della posizione? Per evitare questo, il seguente codice è stato aggiunto al sistema.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType      ev;
                                
        switch (id)
        {
                case CHART_EVENT_OBJECT_DELETE:
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

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

In questo modo, se l'operatore elimina qualcosa che non dovrebbe, l'EA sostituirà rapidamente l'oggetto eliminato.

Il seguente video mostra come funziona attualmente il sistema. Ci sono state anche alcune modifiche che non ho trattato nell'articolo, perché si trattava di modifiche minori che non avrebbero influito sulla spiegazione.




Conclusioni

Anche se il sistema sembra essere completo e potresti voler fare trading con esso, devo avvertirti che non è ancora completo. Questo articolo avrebbe dovuto mostrarti come aggiungere e modificare le cose per avere un sistema di ordini molto più pratico e facile da usare. Ma manca ancora il sistema responsabile dello spostamento delle posizioni e questo è ciò che renderà l'EA molto istruttivo, pratico e intuitivo da usare. Ma lo lasceremo per il prossimo articolo.

L'allegato contiene il sistema allo stato attuale di sviluppo.


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

File allegati |
Impara come progettare un sistema di trading tramite Bull’s Power Impara come progettare un sistema di trading tramite Bull’s Power
Benvenuti ad un nuovo articolo della nostra serie su come imparare a progettare un sistema di trading attraverso gli indicatori tecnici più popolari. In questo articolo impareremo a conoscere un nuovo indicatore tecnico e a progettare un sistema di trading in base ad esso, questo indicatore è il Bull's Power.
Sviluppare un Expert Advisor per il trading da zero (Parte 20): Nuovo sistema di ordini (III) Sviluppare un Expert Advisor per il trading da zero (Parte 20): Nuovo sistema di ordini (III)
Continuiamo a implementare il nuovo sistema di ordini. La creazione di un tale sistema richiede una buona padronanza di MQL5, nonché una comprensione di come funziona effettivamente la piattaforma MetaTrader 5 e quali risorse fornisce.
Impara come progettare un sistema di trading tramite VIDYA Impara come progettare un sistema di trading tramite VIDYA
Benvenuti in un nuovo articolo della nostra serie su come progettare un sistema di trading con gli indicatori tecnici più popolari. In questo articolo conosceremo un nuovo strumento tecnico e impareremo a progettare un sistema di trading tramite Variable Index Dynamic Average (VIDYA).
Algoritmi di ottimizzazione della popolazione: Colonia di api artificiali (ABC) Algoritmi di ottimizzazione della popolazione: Colonia di api artificiali (ABC)
In questo articolo studieremo l'algoritmo di una colonia di api artificiali e integreremo le nostre conoscenze con nuovi principi dello studio degli spazi funzionali. In questo articolo presenterò la mia interpretazione della versione classica dell'algoritmo.