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

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

MetaTrader 5Trading | 30 ottobre 2023, 10:56
389 0
Daniel Jose
Daniel Jose

Introduzione

Nel precedente articolo Sviluppare un Expert Advisor per il trading da zero (Parte 22), abbiamo sviluppato un sistema per spostare gli ordini pendenti e i livelli di stop delle posizioni. Sebbene questo metodo sia relativamente sicuro (perché riflette ciò che si trova sul server di trading), non è il modo migliore per spostare rapidamente i livelli.

Il problema è che ogni volta che modifichiamo qualcosa utilizzando il mouse, questo evento viene inviato al server e dobbiamo quindi attendere una risposta. Il problema è legato al fatto che l'evento viene inviato ad ogni tick, cioè se volessimo spostare un livello di più tick alla volta, dovremmo passare attraverso tutti i valori intermedi, il che rende l'intero processo molto lento. In questo modo implementiamo le modifiche al codice per renderlo più flessibile e cambiare i livelli più velocemente.


1.0. Pianificazione

Per implementare le modifiche, dobbiamo fare una cosa molto semplice: Non informare il server di tutti i cambiamenti, informarlo solo della modifica richiesta. Anche solo facendo questo renderemo già il tutto funzionante anche se non saremo assolutamente sicuri che tutto sia esattamente come lo stiamo facendo.

Vediamo ora dove dobbiamo modificare il codice. Usiamo una funzione separata mostrata di seguito:

#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP))
                void MoveSelection(double price, uint keys)
                        {
                                static string memStr = NULL;
                                static ulong ticket = 0;
                                static eIndicatorTrade it;
                                eEventType ev;
                                double tp, sl, pr;
                                bool isPending;
                                
                                string sz0 = m_TradeLine.GetObjectSelected();
                                
                                if (sz0 != NULL)
                                {
                                        if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev);
                                        isPending = OrderSelect(ticket);
                                        switch (it)
                                        {
                                                case IT_TAKE:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP));
                                                        else ModifyPosition(ticket, price, macroGetPrice(IT_STOP));
                                                        break;
                                                case IT_STOP:
                                                        if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price);
                                                        else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price);
                                                        break;
                                                case IT_PENDING:
                                                        pr = macroGetPrice(IT_PENDING);
                                                        tp = macroGetPrice(IT_TAKE);
                                                        sl = macroGetPrice(IT_STOP);
                                                        ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr));
                                                        break;
                                        }
                                };
                        }
#undef macroGetPrice

Ma insieme alle modifiche all'interno della funzione, dovremo implementare anche alcune modifiche relative agli eventi del mouse. Questo è ciò su cui ci concentreremo per primo.

I segmenti evidenziati devono essere sostituiti con qualcos'altro per rappresentare le nuove posizioni che verranno utilizzate. Ma tutte le modifiche dovrebbero essere comprensibili all'utente.

Ho trovato un modo efficace per rendere tutto facile da capire e allo stesso tempo funzionale, senza apportare grandi modifiche all'intera struttura del codice. Questo per creare un'etichetta di indicazione fantasma che non sarà visibile finché non sarà necessaria. Fortunatamente, MetaTrader 5 fornisce un modo molto semplice per farlo. Pertanto, questo articolo sarà molto facile da comprendere per coloro che hanno già familiarità con il materiale degli articoli precedenti di questa serie.


2.0. Implementazione

Per implementare un'etichetta fantasma, la creeremo semplicemente con una reale. Questa sarà l'ombra esatta di una vera etichetta finché non passeremo a lavorare con i prezzi come abbiamo mostrato nell'articolo precedente. A questo punto, l'etichetta fantasma apparirà sul grafico mentre quella reale si muove. Ciò ti consentirà di confrontare facilmente ciò che sta accadendo e di capire se apportare o meno modifiche.


2.0.1. Creazione di un'etichetta di indicazione fantasma

Tutte le modifiche vengono implementate all'interno della classe C_IndicatorTradeView. Cominciamo definendo tre nuove direttive:

#define def_IndicatorGhost      "G"
#define def_IndicatorReal       "R"
#define def_IndicatorGhostColor clrDimGray

Questo farà sì che MetaTrader 5 lavori per noi. La regola è questa: prima creiamo un'etichetta fantasma, poi ne creiamo una vera. Pertanto, MetaTrader 5 si prenderà cura di non mostrare l'etichetta fantasma fino al momento in cui avremo davvero bisogno di vederla. Poiché questo viene fatto dalla stessa MetaTrader 5, risparmieremo molto sulla programmazione.

Un dettaglio importante: se vuoi cambiare il colore del fantasma, cambia semplicemente il colore specificato nella parte selezionata.

Pertanto, il passo successivo è modificare la funzione che consente di generare nomi univoci.

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

Le parti evidenziate sono state aggiunte o modificate rispetto alla versione precedente. Possiamo lasciare che MetaTrader 5 generi nomi univoci. Quindi non dobbiamo preoccuparci di questo. Nota che ho aggiunto valori agli eventi. L'ho fatto per evitare che il fantasma ricevesse eventi e cercasse di prenderne il controllo.

Il passo successivo è ovvio: dobbiamo creare l'etichetta stessa:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2, cor3;
                                string sz0, sz1;
                                
                                switch (it)
                                {
                                        case IT_TAKE    :
                                                cor1 = clrForestGreen;
                                                cor2 = clrDarkGreen;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_STOP    :
                                                cor1 = clrFireBrick;
                                                cor2 = clrMaroon;
                                                cor3 = clrNONE;
                                                break;
                                        case IT_PENDING:
                                                cor1 = clrCornflowerBlue;
                                                cor2 = clrDarkGoldenrod;
                                                cor3 = def_ColorVolumeEdit;
                                                break;
                                        case IT_RESULT  :
                                        default:
                                                cor1 = clrDarkBlue;
                                                cor2 = clrDarkBlue;
                                                cor3 = def_ColorVolumeResult;
                                                break;
                                }
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor);
                                m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2);
                                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                                if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor);
                                m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1);
                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 92, 22);
                                                m_BackGround.Size(sz1, 92, 22);
                                                break;
                                        case IT_RESULT:
                                                m_BackGround.Size(sz1, 84, 34);
                                                break;
                                }
                                m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose);
                                m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0);
                                m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0);
                                m_EditInfo1.Size(sz0, 60, 14);
                                m_EditInfo1.Size(sz1, 60, 14);
                                if (it != IT_RESULT)
                                {
                                        m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor);
                                        m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                                        m_BtnMove.Size(sz1, 21, 21);
                                }else
                                {
                                        m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0);
                                        m_EditInfo2.Size(sz1, 60, 14);
                                }
                        }

Tutte le linee evidenziate creano fantasmi. Una cosa può sembrare strana: perché non riproduciamo tutti gli elementi. Il fantasma infatti non è una copia esatta dell'etichetta reale ma solo la sua ombra. Pertanto, non è necessario creare tutti gli elementi. La vera etichetta è quella che definirà cosa dovrebbe accadere mentre il fantasma serve solo come riferimento a ciò che vedrà il server di trade.

Adesso arriva la parte che richiede uno studio più approfondito, dove MetaTrader 5 lavorerà davvero sodo. Potresti pensare che organizzare gli oggetti nei posti giusti richieda molto lavoro, ma guarda cosa è effettivamente cambiato nel codice sorgente.

#define macroSetAxleY(A, B)     {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y);                              \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y);                                 \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y);                                 \
                m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0));      \
                m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y));         \
                m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1);  \
                                }
                                                                        
#define macroSetAxleX(A, B, C)  {                                                       \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B);      \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B);         \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3);     \
                m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21);    \
                m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80);      \
                m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21);  \
                                }                                                                               
//+------------------------------------------------------------------+
inline void ReDrawAllsIndicator(void)
                        {
                                int             max = ObjectsTotal(Terminal.Get_ID(), -1, -1);
                                ulong           ticket;
                                double          price;
                                eIndicatorTrade it;
                                eEventType      ev;
                                
                                for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev))
                                        PositionAxlePrice(ticket, it, price);
                        }
//+------------------------------------------------------------------+
inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price)
                        {
                                int x, y, desl;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price));
                                macroSetAxleY(it, true);
                                macroSetAxleY(it, false);
                                switch (it)
                                {
                                        case IT_TAKE: desl = 110; break;
                                        case IT_STOP: desl = 220; break;
                                        default: desl = 0;
                                }
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true);
                                macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false);
                        }
#undef macroSetAxleX
#undef macroSetAxleY

Tutto qui? Cambiamo semplicemente le macro? Vero, non è necessario ricreare l'intero codice, lo modifichiamo semplicemente. Tutto quello che realmente dobbiamo fare è dire a MetaTrader 5 il nome dell'oggetto che stiamo manipolando, mentre MetaTrader 5 farà il resto per noi. Non abbiamo bisogno di creare una serie di funzioni per questo. Aggiungi semplicemente le parti evidenziate.

Un'altra funzione da modificare qui è mostrata di seguito:

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;
                                
        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Qui abbiamo aggiunto i segmenti selezionati. Ecco come viene selezionato il nostro fantasma. Riflette accuratamente ciò che sta accadendo all'etichetta reale. In effetti, si riflette troppo bene, così bene che dovremo implementare poche altre cose per farlo funzionare correttamente. Il codice non è sbagliato, ma il fantasma è troppo strettamente correlato all'etichetta reale, cosa che in realtà dovremmo evitare.

L'ultima funzione da modificare qui è mostrata di seguito:

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

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

I frammenti evidenziati sono cambiati, niente di speciale qui.


2.0.2. Separare il fantasma dal reale

Le modifiche apportate nella sezione precedente creano un fantasma. Ma abbiamo un problema — è troppo vicino all'oggetto reale. Questo è qualcosa che a prima vista sarà molto difficile da implementare. Ma quando guardiamo il codice, vediamo che abbiamo già una soluzione. Tuttavia, questa soluzione è nel posto sbagliato. Dobbiamo cambiare il luogo in cui si trova la soluzione e renderla più visibile in tutta la classe.

La soluzione è mostrata nel seguente codice:

                void DispatchMessage(int id, long lparam, double dparam, string sparam)
                        {
                                ulong   ticket;
                                double  price, tp, sl;
                                bool            isBuy,
                                                        bKeyBuy,
                                                        bKeySell,
                                                        bEClick;
                                long            info;
                                datetime        dt;
                                uint            mKeys;
                                eIndicatorTrade         it;
                                eEventType                      ev;
                                
                                static bool bMounting = false, bIsDT = false, bIsMove = false;
                                static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_MOUSE_MOVE:

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

La soluzione è il codice evidenziato. Ma come è possibile? Ricorda che quando effettui un ordine pendente, il sistema riesce a creare e manipolare i dati in modo che alla fine tu abbia una rappresentazione delle etichette sul grafico che indicano dove verrà inserito l'ordine. Questo viene fatto nel seguente codice:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
        if (bKeyBuy != bKeySell)
        {
                if (!bMounting)
                {
                        Mouse.Hide();
                        bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl);
                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange);
                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange);
                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                        bMounting = true;
                }
                tp = price + (bKeyBuy ? valueTp : (-valueTp));
                sl = price + (bKeyBuy ? (-valueSl) : valueSl);
                UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy);
                if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT);
                }else if (bMounting)
                {
                        UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false);
                        Mouse.Show();
                        memLocal = 0;
                        bMounting = false;

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

A seconda se si preme SHIFT per acquistare o CTRL per vendere, il sistema creerà una rappresentazione dell'ordine pendente in fase di creazione. Verrà visualizzato direttamente sul grafico. I valori ​da utilizzare come Take Profit o Stop Loss vengono catturati all'interno del Chart Trade, quindi si sposta la rappresentazione sul punto in cui si desidera inserire l'ordine e successivamente, facendo clic con il pulsante sinistro del mouse si comunica al sistema che un ordine deve essere inserito lì . Non appena si sposta nuovamente il mouse, l'etichetta utilizzata per rappresentare l'ordine viene rimossa e l'indicazione dell'ordine rimane.

Finora non c'era niente di speciale. Ma se guardi nel codice, vedrai quanto segue:

// ... CHARTEVENT_MOUSE_MOVE code....

        }else if ((!bMounting) && (bKeyBuy == bKeySell))
        {
                if (bEClick)
                {
                        bIsMove = false;
                        m_TradeLine.SpotLight();
                }
                MoveSelection(price, mKeys);
        }
break;

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

Pertanto, quando non stiamo creando un ordine pendente e i tasti vengono rilasciati, inviamo la posizione del prezzo del mouse a qualche etichetta che può essere selezionata. Questo viene fatto nella riga evidenziata. Non appena avremo cliccato con il tasto sinistro, termineremo questo trasferimento, l'indicatore verrà deselezionato e questo cambierà lo stato di una variabile che è attualmente locale, bIsMove. Ma cambiamo la situazione. Lo stato di questa variabile viene modificato solo da un altro evento all'interno della funzione DispatchMessage. Questo evento assomiglia a questo:

// ... Code ....

        case EV_MOVE:
                if (bIsMove)
                {
                        m_TradeLine.SpotLight();
                        bIsMove = false;
                }else
                {
                        m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                        bIsMove = true;
                }
        break;

Questo codice cambierà lo stato della variabile bIsMove e allo stesso tempo cambierà l'etichetta per mostrare se è selezionata o meno.

Pertanto, se rendiamo questa variabile visibile in tutta la classe, possiamo separare l'etichetta fantasma da quella reale e sarà possibile manipolare solo il reale o solo il fantasma — dipende da cosa trovi più interessante. Ma qui cambieremo l’etichetta reale, mentre il fantasma mostrerà ciò che vede il server di trading.

In questo modo, non dobbiamo pasticciare molto con il codice, basta regolare pochi dettagli. Una volta eseguito il clic sinistro e spostato un oggetto, verrà inviato un ordine per modificare un ordine in sospeso o un livello di stop.

Vediamo come questo viene fatto nella pratica. Innanzitutto, creiamo una variabile privata.

bool    m_bIsMovingSelect;

Ciò rispecchia quanto ho spiegato sopra. Ma deve essere inizializzato.

C_IndicatorTradeView() : m_bIsMovingSelect(false) {}

Ora andiamo su DispatchMessage e usiamolo al posto di bIsMove.

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

// ... Internal code...

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Internal code...

                                }else if ((!bMounting) && (bKeyBuy == bKeySell))
                                {
                                        if (bEClick)
                                        {
                                                m_bIsMovingSelect = false;
                                                m_TradeLine.SpotLight();
                                        }
                                        MoveSelection(price, mKeys);
                                }
                                break;

// ... Internal code...

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

// ... Internal code....

                                case EV_MOVE:
                                        if (m_bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_bIsMovingSelect = false;
                                        }else
                                        {
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                                m_bIsMovingSelect = true;
                                        }
                                        break;
                        }
                        break;
                }
}

Le modifiche sono evidenziate. Quindi ora l'intera classe sa quando lavoriamo o meno con qualche etichetta scelta dall'utente. In questo modo possiamo separare l'etichetta fantasma dall'etichetta reale e avere una rappresentazione più accurata.


2.0.3. Muovere solo ciò che conta

Nei due argomenti precedenti abbiamo creato e apportato alcune correzioni al sistema per avere un'etichetta di indicazione fantasma. Ora dobbiamo spostare i componenti e per questo dovremo lavorare un po'. Il primo passo è creare una struttura per memorizzare le varie informazioni di cui abbiamo bisogno per evitare chiamate eccessive tra le funzioni. La struttura è mostrata di seguito:

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

Il segmento evidenziato esisteva precedentemente nel codice, ma ora fa parte della struttura. Non preoccuparti. Andando oltre, diventerà più chiaro il motivo per cui la struttura ha questi elementi.

Qui considereremo le funzioni che sono state modificate e richiedono spiegazioni. La prima è SetTextValue.

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;

        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        value0 = value0 / Terminal.GetVolumeMinimal();
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor);
                        break;
        }
}

Quando spostiamo qualcosa, non vogliamo che il fantasma segua i nuovi dati, vogliamo che rimanga fermo, mostrando dov'era l'etichetta. Questo può essere fatto facilmente aggiungendo i punti evidenziati. Pertanto, il fantasma resta fermo mentre l'etichetta si muove liberamente. Qui non avremo il movimento in sé, ma l'indicazione dei valori ​​che sono sul server.

Questo è seguito dal codice del movimento mostrato di seguito:

void MoveSelection(double price)
{
        double tp, sl;
                                
        if (!m_InfoSelection.bIsMovingSelect) return;
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_STOP:
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
                case IT_PENDING:
                        tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr);
                        sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr);
                        UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        break;
        }
}

Prestate attenzione alla parte evidenziata. Regola il movimento in modo che il sistema indichi correttamente i dati. Questo può sembrare strano, ma la funzione UpdateINfosIndicators ha un'altra soluzione. Se non lo facciamo adesso, avremo problemi in seguito. Altre funzioni sono abbastanza semplici e non richiedono spiegazioni.

void SetPriceSelection(double price)
{
        bool isPending;
        if (!m_InfoSelection.bIsMovingSelect) return;
        isPending = OrderSelect(m_InfoSelection.ticket);
        m_InfoSelection.bIsMovingSelect = false;
        m_TradeLine.SpotLight();
        switch (m_InfoSelection.it)
        {
                case IT_TAKE:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl);
                        else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl);
                        break;
                case IT_STOP:
                        if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price);
                        else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price);
                        break;
                case IT_PENDING:
                        ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr));
                        break;
        }
}

La funzione di cui sopra informerà il server su cosa sta succedendo e quali sono i nuovi dati. Tieni presente che qualche indicatore deve essere selezionato nelle righe evidenziate, altrimenti la richiesta al server non verrà effettuata.

L'ultima funzione consigliata è mostrata di seguito:

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

Ci sono alcune cose da notare su questa funzione, ma fai attenzione alla differenza rispetto alla versione precedente: qui stiamo riutilizzando molto più codice. Pertanto, se da qualche altra parte abbiamo un errore nel codice, possiamo notarlo rapidamente e correggerlo. In precedenza, il codice di questa funzione era un po' instabile, con alcune correzioni e difetti in altre parti. Uno dei punti principali era che quando piazzavamo un ordine pendente, c'era un sistema separato per spostare gli indicatori, e ora abbiamo un unico sistema, lo stesso utilizzato per piazzare ordini sul grafico. Viene utilizzato anche per spostare gli oggetti, ovvero ora lo stesso evento CHARTEVENT_MOUSE_MOVE viene utilizzato sia per inserire un ordine pendente che per spostare oggetti. Può sembrare una cosa da poco, ma rende visibile qualsiasi modifica al codice e, se abbiamo un problema, verrà visualizzato finché utilizziamo l'evento del mouse.


Conclusioni

Guarda il video qui sotto per avere un'idea più chiara di cosa succede con le modifiche apportate. Vedrai che ora ci servono solo pochi dettagli in più per rendere l'EA completo in termini di sistema degli ordini.


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

File allegati |
Come utilizzare i modelli ONNX in MQL5 Come utilizzare i modelli ONNX in MQL5
ONNX (Open Neural Network Exchange) è un formato aperto creato per rappresentare modelli di machine learning. In questo articolo considereremo come creare un modello CNN-LSTM per prevedere le serie temporali finanziarie. Mostreremo anche come utilizzare il modello ONNX creato in un Expert Advisor MQL5.
Sviluppare un Expert Advisor per il trading da zero (Parte 22): Nuovo sistema di ordini (V) Sviluppare un Expert Advisor per il trading da zero (Parte 22): Nuovo sistema di ordini (V)
Oggi continueremo a sviluppare il nuovo sistema di ordini. Non è così facile implementare un nuovo sistema poiché spesso incontriamo problemi che complicano notevolmente il processo. Quando compaiono questi problemi, dobbiamo fermarci e rianalizzare la direzione in cui ci stiamo muovendo.
Algoritmi di ottimizzazione della popolazione: Algoritmo della Lucciola (Firefly FA) Algoritmi di ottimizzazione della popolazione: Algoritmo della Lucciola (Firefly FA)
In questo articolo prenderò in considerazione il metodo di ottimizzazione dell'Algoritmo Firefly(FA). Grazie alla modifica, l'algoritmo si è trasformato da outsider a vero leader della classifica.
Avvio di MetaTrader VPS per la prima volta: Istruzioni passo-passo Avvio di MetaTrader VPS per la prima volta: Istruzioni passo-passo
Tutti coloro che hanno utilizzato robot di trading o abbonamenti a segnali prima o poi riconoscono la necessità di noleggiare un server di hosting affidabile 24/7 per la loro piattaforma di trading. Consigliamo di utilizzare MetaTrader VPS per una serie di motivi. Potete pagare comodamente il servizio e gestire l'abbonamento attraverso il vostro account MQL5.community.